import { fetchApi } from '@/lib/server-api-util'; 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 { Alert } from 'react-native'; const BACKGROUND_UPLOAD_TASK = 'background-upload-task'; // 获取指定时间范围内的媒体文件 export const getMediaByDateRange = async (startDate: Date, endDate: Date) => { try { const { status } = await MediaLibrary.requestPermissionsAsync(); if (status !== 'granted') { console.log('Media library permission not granted'); return []; } // 获取媒体资源 const media = await MediaLibrary.getAssetsAsync({ mediaType: ['photo', 'video'], first: 100, // 每次最多获取100个 sortBy: [MediaLibrary.SortBy.creationTime], createdAfter: startDate.getTime(), createdBefore: endDate.getTime(), }); return media.assets; } catch (error) { console.error('Error getting media by date range:', error); return []; } }; // 压缩图片 const compressImage = async (uri: string): Promise<{ uri: string; file: File }> => { try { const manipResult = await ImageManipulator.manipulateAsync( uri, [ { resize: { width: 1200, height: 1200, }, }, ], { compress: 0.7, format: ImageManipulator.SaveFormat.JPEG, base64: false, } ); const response = await fetch(manipResult.uri); const blob = await response.blob(); const filename = uri.split('/').pop() || `image_${Date.now()}.jpg`; const file = new File([blob], filename, { type: 'image/jpeg' }); return { uri: manipResult.uri, file }; } catch (error) { console.error('Error compressing image:', error); throw error; } }; // 获取上传URL const getUploadUrl = async (file: File, metadata: Record = {}): Promise<{ upload_url: string; file_id: string }> => { const body = { filename: file.name, content_type: file.type, file_size: file.size, metadata: { ...metadata, originalName: file.name, fileType: file.type.startsWith('video/') ? 'video' : 'image', isCompressed: 'true', }, }; return await fetchApi<{ upload_url: string; file_id: string }>("/file/generate-upload-url", { method: 'POST', body: JSON.stringify(body) }); }; // 确认上传 const confirmUpload = async (file_id: string) => { return await fetchApi('/file/confirm-upload', { method: 'POST', body: JSON.stringify({ file_id }) }); }; // 新增素材 const addMaterial = async (file: string, compressFile: string) => { await fetchApi('/material', { method: 'POST', body: JSON.stringify([{ "file_id": file, "preview_file_id": compressFile }]) }).catch((error) => { // console.log(error); }) } // 上传文件到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); }); }; // 检查并请求媒体库权限 const checkMediaLibraryPermission = async (): Promise<{ hasPermission: boolean, status?: string }> => { try { const { status, accessPrivileges } = await MediaLibrary.getPermissionsAsync(); // 如果已经授权,直接返回 if (status === 'granted' && accessPrivileges === 'all') { return { hasPermission: true, status }; } // 如果没有授权,请求权限 const { status: newStatus, accessPrivileges: newPrivileges } = await MediaLibrary.requestPermissionsAsync(); const isGranted = newStatus === 'granted' && newPrivileges === 'all'; if (!isGranted) { console.log('Media library permission not granted or limited access'); } return { hasPermission: isGranted, status: newStatus }; } catch (error) { console.error('Error checking media library permission:', error); return { hasPermission: false }; } }; // 处理单个媒体文件上传 const processMediaUpload = async (asset: MediaLibrary.Asset) => { try { // 检查权限 const { hasPermission } = await checkMediaLibraryPermission(); if (!hasPermission) { throw new Error('No media library permission'); } const isVideo = asset.mediaType === 'video'; // 上传原始文件 const uploadOriginalFile = async () => { const response = await fetch(asset.uri); const blob = await response.blob(); const filename = asset.filename || `${isVideo ? 'video' : 'image'}_${Date.now()}_original.${isVideo ? 'mp4' : 'jpg'}`; 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 }); // 获取上传URL并上传原始文件 const { upload_url, file_id } = await getUploadUrl(file, { originalUri: asset.uri, creationTime: asset.creationTime, mediaType: isVideo ? 'video' : 'image', isCompressed: false }); await uploadFile(file, upload_url); await confirmUpload(file_id); console.log(`Successfully uploaded original: ${filename}`); return { success: true, file_id }; }; // 上传压缩文件(仅图片) const uploadCompressedFile = async () => { if (isVideo) return { success: true, file_id: null }; // 视频不压缩 try { const { file: compressedFile } = await compressImage(asset.uri); const filename = asset.filename ? `compressed_${asset.filename}` : `image_${Date.now()}_compressed.jpg`; console.log("Compressed file prepared for upload:", { name: filename, size: compressedFile.size, type: compressedFile.type }); // 获取上传URL并上传压缩文件 const { upload_url, file_id } = await getUploadUrl(compressedFile, { originalUri: asset.uri, creationTime: asset.creationTime, mediaType: 'image', isCompressed: true }); await uploadFile(compressedFile, upload_url); await confirmUpload(file_id); console.log(`Successfully uploaded compressed: ${filename}`); return { success: true, file_id }; } catch (error) { console.error('Error uploading compressed file:', error); return { success: false, error }; } }; // 同时上传原始文件和压缩文件 console.log("Asset info:", asset); const [originalResult, compressedResult] = await Promise.all([ uploadOriginalFile(), uploadCompressedFile() ]); console.log("originalResult", originalResult); console.log("compressedResult", compressedResult); addMaterial(originalResult.file_id, compressedResult.file_id) return { originalSuccess: originalResult.success, compressedSuccess: compressedResult.success, fileIds: { original: originalResult.file_id, compressed: compressedResult.file_id } }; } catch (error) { console.error('Error in processMediaUpload:', error); if (error.message === 'No media library permission') { throw error; } return { originalSuccess: false, compressedSuccess: false, error }; } }; // 注册后台任务 export const registerBackgroundUploadTask = async () => { try { // 检查是否已注册 const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_UPLOAD_TASK); if (isRegistered) { await BackgroundFetch.unregisterTaskAsync(BACKGROUND_UPLOAD_TASK); } // 注册后台任务 await BackgroundFetch.registerTaskAsync(BACKGROUND_UPLOAD_TASK, { minimumInterval: 15 * 60, // 15分钟 stopOnTerminate: false, // 应用关闭后继续运行 startOnBoot: true, // 系统启动后自动运行 }); console.log('Background upload task registered'); return true; } catch (error) { console.error('Error registering background task:', error); return false; } }; // 定义后台任务 TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => { try { console.log('Background upload task started'); // 检查并请求媒体库权限 const { hasPermission } = await checkMediaLibraryPermission(); if (!hasPermission) { console.log('Media library permission not granted'); // 返回 NoData 而不是 Failed,这样系统可能会在稍后重试 return BackgroundFetch.BackgroundFetchResult.NoData; } // 获取过去24小时内的媒体 const now = new Date(); const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); const media = await getMediaByDateRange(yesterday, now); console.log("media", media); if (media.length === 0) { console.log('No media found in the specified time range'); return BackgroundFetch.BackgroundFetchResult.NoData; } 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; } // 其他错误继续处理下一个文件 } } console.log('Background upload task completed'); return BackgroundFetch.BackgroundFetchResult.NewData; } catch (error) { console.error('Error in background upload task:', error); return BackgroundFetch.BackgroundFetchResult.Failed; } }); // 手动触发上传 export const triggerManualUpload = async (startDate: Date, endDate: Date) => { try { console.log('Starting manual upload...'); console.log("startDate", startDate); console.log("endDate", endDate); const media = await getMediaByDateRange(startDate, endDate); console.log("media", media); if (media.length === 0) { Alert.alert('提示', '在指定时间范围内未找到媒体文件'); return []; } const results = []; for (const item of media) { try { const result = await processMediaUpload(item); results.push({ id: item.id, originalSuccess: result.originalSuccess, compressedSuccess: result.compressedSuccess, fileIds: result.fileIds, }); } catch (error) { console.error(`Error uploading ${item.filename}:`, error); results.push({ id: item.id, originalSuccess: false, compressedSuccess: false, error: error instanceof Error ? error.message : 'Unknown error', }); } } const originalSuccessCount = results.filter(r => r.originalSuccess).length; const compressedSuccessCount = results.filter(r => r.compressedSuccess).length; Alert.alert('上传完成', `成功上传 ${originalSuccessCount}/${media.length} 个原始文件,${compressedSuccessCount}/${media.length} 个压缩文件`); return results; } catch (error) { console.error('Error in manual upload:', error); Alert.alert('错误', '上传过程中出现错误'); throw error; } };