384 lines
13 KiB
TypeScript
384 lines
13 KiB
TypeScript
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<string, any> = {}): 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<void> => {
|
||
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;
|
||
}
|
||
};
|