From 19ed3bba527b3013c606cb7250850300ebd6c667 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Tue, 15 Jul 2025 20:41:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/owner.tsx | 2 +- components/file-upload/backgroundUploader.ts | 192 ++++++++++++------- components/owner/userName.tsx | 2 +- 3 files changed, 120 insertions(+), 76 deletions(-) diff --git a/app/(tabs)/owner.tsx b/app/(tabs)/owner.tsx index 5ab4693..1c6ec85 100644 --- a/app/(tabs)/owner.tsx +++ b/app/(tabs)/owner.tsx @@ -71,7 +71,7 @@ export default function OwnerPage() { {/* 资源数据 */} - } style={{ flex: 1 }} isFormatBytes={true} /> + } style={{ flex: 1 }} isFormatBytes={true} /> } style={{ flex: 1 }} /> {/* 数据统计 */} diff --git a/components/file-upload/backgroundUploader.ts b/components/file-upload/backgroundUploader.ts index 274d9d8..4582dca 100644 --- a/components/file-upload/backgroundUploader.ts +++ b/components/file-upload/backgroundUploader.ts @@ -4,6 +4,7 @@ import * as FileSystem from 'expo-file-system'; 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'; import { transformData } from '../utils/objectFlat'; @@ -14,6 +15,30 @@ type ExtendedAsset = MediaLibrary.Asset & { const BACKGROUND_UPLOAD_TASK = 'background-upload-task'; // 设置最大并发数 const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件 +// 在 CONCURRENCY_LIMIT 定义后添加 +const limit = pLimit(CONCURRENCY_LIMIT); + +// 获取文件扩展名 +const getFileExtension = (filename: string) => { + return filename.split('.').pop()?.toLowerCase() || ''; +}; + +// 获取 MIME 类型 +const getMimeType = (filename: string, isVideo: boolean) => { + if (!isVideo) return 'image/jpeg'; + + const ext = getFileExtension(filename); + switch (ext) { + case 'mov': + return 'video/quicktime'; + case 'mp4': + return 'video/mp4'; + case 'm4v': + return 'video/x-m4v'; + default: + return 'video/mp4'; // 默认值 + } +}; // 将 HEIC 图片转化 const convertHeicToJpeg = async (uri: string): Promise => { @@ -274,42 +299,67 @@ const processMediaUpload = async (asset: ExtendedAsset) => { const uploadOriginalFile = async () => { try { let fileToUpload: File; - let mimeType = isVideo ? 'video/mp4' : 'image/jpeg'; + const isVideo = asset.mediaType === 'video'; + const mimeType = getMimeType(asset.filename || '', isVideo); + + // 生成文件名,保留原始扩展名 let filename = asset.filename || - `${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? 'mp4' : 'jpg'}`; + `${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? (getFileExtension(asset.filename || 'mp4') || 'mp4') : 'jpg'}`; // 处理 HEIC 格式 if (filename.toLowerCase().endsWith('.heic') || filename.toLowerCase().endsWith('.heif')) { - try { - fileToUpload = await convertHeicToJpeg(asset.uri); - filename = filename.replace(/\.(heic|heif)$/i, '.jpg'); - } catch (conversionError) { - console.error('HEIC conversion failed, trying fallback:', conversionError); - // 如果转换失败,尝试使用 MediaLibrary 获取文件 - try { - const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id); - if (assetInfo.localUri) { - const response = await fetch(assetInfo.localUri); - const blob = await response.blob(); - fileToUpload = new File([blob], filename, { type: 'image/jpeg' }); - } else { - throw new Error('No local URI available for the asset'); - } - } catch (fallbackError) { - console.error('Fallback method also failed:', fallbackError); - // 如果所有方法都失败,尝试直接使用原始 URI - const response = await fetch(asset.uri); - const blob = await response.blob(); - fileToUpload = new File([blob], filename, { type: 'image/jpeg' }); - } - } + fileToUpload = await convertHeicToJpeg(asset.uri); + filename = filename.replace(/\.(heic|heif)$/i, '.jpg'); } else { - // 普通图片处理 - const response = await fetch(asset.uri); + // 获取资源信息 + const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id, { + shouldDownloadFromNetwork: true + }); + + if (!assetInfo.localUri) { + throw new Error('无法获取资源的本地路径'); + } + + // 获取文件扩展名 + const fileExtension = getFileExtension(assetInfo.filename || '') || + (isVideo ? 'mp4' : 'jpg'); + + // 确保文件名有正确的扩展名 + if (!filename.toLowerCase().endsWith(`.${fileExtension}`)) { + const baseName = filename.split('.')[0]; + filename = `${baseName}.${fileExtension}`; + } + + // 获取文件内容 + const response = await fetch(assetInfo.localUri); const blob = await response.blob(); + + // 创建文件对象 fileToUpload = new File([blob], filename, { type: mimeType }); + console.log('文件准备上传:', { + name: fileToUpload.name, + type: fileToUpload.type, + size: fileToUpload.size + }); } - let exifData = asset?.exif ? { ...transformData({ ...asset, exif: { ...asset?.exif, '{MakerApple}': undefined } }) } : {}; + + // 准备元数据 + let exifData = {}; + if (asset.exif) { + try { + exifData = transformData({ + ...asset, + exif: { + ...asset.exif, + '{MakerApple}': undefined + } + }); + } catch (exifError) { + console.warn('处理 EXIF 数据时出错:', exifError); + } + } + + // 获取上传 URL const { upload_url, file_id } = await getUploadUrl(fileToUpload, { originalUri: asset.uri, creationTime: asset.creationTime, @@ -319,26 +369,31 @@ const processMediaUpload = async (asset: ExtendedAsset) => { GPSVersionID: undefined }); + // 上传文件 await uploadFile(fileToUpload, upload_url); await confirmUpload(file_id); - return { success: true, file_id }; - } catch (error: unknown) { - if (error instanceof Error) { - console.error('Error in uploadOriginalFile:', { - message: error.message, - name: error.name, - stack: error.stack, - asset: { - id: asset.id, - uri: asset.uri, - filename: asset.filename - } - }); - } else { - console.error('An unknown error occurred:', error); - } - throw error; + console.log('文件上传成功:', { + fileId: file_id, + filename: fileToUpload.name, + type: fileToUpload.type + }); + + return { + success: true, + file_id, + filename: fileToUpload.name + }; + + } catch (error: any) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error('上传原始文件时出错:', { + error: errorMessage, + assetId: asset.id, + filename: asset.filename, + uri: asset.uri + }); + throw new Error(`上传失败: ${errorMessage}`); } }; @@ -467,62 +522,51 @@ export const triggerManualUpload = async (startDate: Date, endDate: Date) => { // 分离图片和视频 const photos = media.filter(item => item.mediaType === 'photo'); const videos = media.filter(item => item.mediaType === 'video'); + console.log('videos11111111', videos); - const results = []; + const results: any[] = []; - // 先处理所有图片 - for (const item of photos) { + // 处理所有图片(带并发控制) + const processPhoto = async (item: any) => { try { const result = await processMediaUpload(item); results.push({ id: item.id, ...result }); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; - console.error('Error processing photo upload:', { - error: error instanceof Error ? { - message: error.message, - name: error.name, - stack: error.stack - } : error, - assetId: item.id - }); + } catch (error: any) { results.push({ id: item.id, originalSuccess: false, compressedSuccess: false, - error: errorMessage + error: error.message }); } - } + }; - // 再处理所有视频 - for (const item of videos) { + // 处理所有视频(带并发控制) + const processVideo = async (item: any) => { try { const result = await processMediaUpload(item); results.push({ id: item.id, ...result }); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'; - console.error('Error processing video upload:', { - error: error instanceof Error ? { - message: error.message, - name: error.name, - stack: error.stack - } : error, - assetId: item.id - }); + } catch (error: any) { results.push({ id: item.id, originalSuccess: false, compressedSuccess: false, - error: errorMessage + error: error.message }); } - } + }; + + // 并发处理图片和视频 + await Promise.all([ + ...photos.map(photo => limit(() => processPhoto(photo))), + ...videos.map(video => limit(() => processVideo(video))) + ]); return results; } catch (error) { diff --git a/components/owner/userName.tsx b/components/owner/userName.tsx index ef3beff..ab64c68 100644 --- a/components/owner/userName.tsx +++ b/components/owner/userName.tsx @@ -57,7 +57,7 @@ export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) { {userInfo?.user_info?.avatar_file_url ? :