feat: 照片自动上传

This commit is contained in:
jinyaqiu 2025-07-15 16:25:32 +08:00
parent 5b23951643
commit 5072157a01
5 changed files with 76 additions and 110 deletions

View File

@ -65,14 +65,15 @@
"locationWhenInUsePermission": "允许 $(PRODUCT_NAME) 访问您的位置" "locationWhenInUsePermission": "允许 $(PRODUCT_NAME) 访问您的位置"
} }
], ],
[ // [
"expo-notifications", // "expo-notifications",
{ // {
"color": "#ffffff", // "color": "#ffffff",
"defaultChannel": "default", // "defaultChannel": "default",
"enableBackgroundRemoteNotifications": false // "enableBackgroundRemoteNotifications": false,
} // "mode": "client"
], // }
// ],
[ [
"expo-audio", "expo-audio",
{ {

View File

@ -49,7 +49,7 @@ export default function HomeScreen() {
} }
console.log("token111111111", token); console.log("token111111111", token);
if (token) { if (token) {
router.push('/ask') router.push('/user-message')
} else { } else {
router.push('/login') router.push('/login')
} }

View File

@ -6,7 +6,6 @@ module.exports = function (api) {
"nativewind/babel", "nativewind/babel",
], ],
plugins: [ plugins: [
'expo-router/babel',
'react-native-reanimated/plugin', 'react-native-reanimated/plugin',
], ],
}; };

View File

@ -8,28 +8,50 @@ import { Alert } from 'react-native';
const BACKGROUND_UPLOAD_TASK = 'background-upload-task'; const BACKGROUND_UPLOAD_TASK = 'background-upload-task';
// 设置最大并发数 // 设置最大并发数
const CONCURRENCY_LIMIT = 3; // 同时最多上传3个文件 const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件
// 获取指定时间范围内的媒体文件
// 获取指定时间范围内的媒体文件(包含 EXIF 信息)
export const getMediaByDateRange = async (startDate: Date, endDate: Date) => { export const getMediaByDateRange = async (startDate: Date, endDate: Date) => {
console.log("getMediaByDateRange", startDate.getTime(), endDate.getTime());
try { try {
const { status } = await MediaLibrary.requestPermissionsAsync(); const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') { if (status !== 'granted') {
console.log('Media library permission not granted');
return []; return [];
} }
// 获取媒体资源 // 获取媒体资源
const media = await MediaLibrary.getAssetsAsync({ const media = await MediaLibrary.getAssetsAsync({
mediaType: ['photo', 'video'], mediaType: ['photo', 'video'],
first: 100, // 每次最多获取100个 first: 10, // 每次最多获取100个
sortBy: [MediaLibrary.SortBy.creationTime], sortBy: [MediaLibrary.SortBy.creationTime],
createdAfter: startDate.getTime(), createdAfter: startDate.getTime(),
createdBefore: endDate.getTime(), createdBefore: endDate.getTime(),
}); });
return media.assets; console.log("media1111111111", media);
// 为每个资源获取完整的 EXIF 信息
const assetsWithExif = await Promise.all(
media.assets.map(async (asset) => {
try {
if (asset.mediaType === 'photo') {
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id);
return {
...asset,
exif: assetInfo.exif || null,
location: assetInfo.location || null
};
}
return asset; // 视频不处理 EXIF
} catch (error) {
return asset; // 如果获取 EXIF 失败,返回原始资源
}
})
);
return assetsWithExif;
} catch (error) { } catch (error) {
console.error('Error getting media by date range:', error);
return []; return [];
} }
}; };
@ -61,13 +83,13 @@ const compressImage = async (uri: string): Promise<{ uri: string; file: File }>
return { uri: manipResult.uri, file }; return { uri: manipResult.uri, file };
} catch (error) { } catch (error) {
console.error('Error compressing image:', error);
throw error; throw error;
} }
}; };
// 获取上传URL // 获取上传URL
const getUploadUrl = async (file: File, metadata: Record<string, any> = {}): Promise<{ upload_url: string; file_id: string }> => { const getUploadUrl = async (file: File, metadata: Record<string, any> = {}): Promise<{ upload_url: string; file_id: string }> => {
const body = { const body = {
filename: file.name, filename: file.name,
content_type: file.type, content_type: file.type,
@ -103,65 +125,38 @@ const addMaterial = async (file: string, compressFile: string) => {
"preview_file_id": compressFile "preview_file_id": compressFile
}]) }])
}).catch((error) => { }).catch((error) => {
// console.log(error);
}) })
} }
// 上传文件到URL // 上传文件到URL
const uploadFile = async (file: File, uploadUrl: string): Promise<void> => { const uploadFile = async (file: File, uploadUrl: string): Promise<void> => {
console.log('=== Starting file upload ==='); return new Promise((resolve, reject) => {
console.log('File info:', { const xhr = new XMLHttpRequest();
name: file.name,
type: file.type, xhr.open('PUT', uploadUrl);
size: file.size, xhr.setRequestHeader('Content-Type', file.type);
lastModified: file.lastModified
// 进度监听
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
}
};
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('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;
}
}; };
// 检查并请求媒体库权限 // 检查并请求媒体库权限
@ -187,7 +182,6 @@ const checkMediaLibraryPermission = async (): Promise<{ hasPermission: boolean,
status: newStatus status: newStatus
}; };
} catch (error) { } catch (error) {
console.error('Error checking media library permission:', error);
return { hasPermission: false }; return { hasPermission: false };
} }
}; };
@ -213,19 +207,19 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
const mimeType = isVideo ? 'video/mp4' : 'image/jpeg'; const mimeType = isVideo ? 'video/mp4' : 'image/jpeg';
const file = new File([blob], filename, { type: mimeType }); const file = new File([blob], filename, { type: mimeType });
console.log("Original file prepared for upload:", file);
// 获取上传URL并上传原始文件 // 获取上传URL并上传原始文件
const { upload_url, file_id } = await getUploadUrl(file, { const { upload_url, file_id } = await getUploadUrl(file, {
originalUri: asset.uri, originalUri: asset.uri,
creationTime: asset.creationTime, creationTime: asset.creationTime,
mediaType: isVideo ? 'video' : 'image', mediaType: isVideo ? 'video' : 'image',
isCompressed: false isCompressed: false,
...asset?.exif,
GPSVersionID: undefined
}); });
await uploadFile(file, upload_url); await uploadFile(file, upload_url);
await confirmUpload(file_id); await confirmUpload(file_id);
console.log(`Successfully uploaded original: ${filename}`);
return { success: true, file_id }; return { success: true, file_id };
}; };
@ -239,13 +233,6 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
`compressed_${asset.filename}` : `compressed_${asset.filename}` :
`image_${Date.now()}_compressed.jpg`; `image_${Date.now()}_compressed.jpg`;
console.log("Compressed file prepared for upload:", {
name: filename,
size: compressedFile.size,
type: compressedFile.type
});
console.log("compressedFile", compressedFile);
// 获取上传URL并上传压缩文件 // 获取上传URL并上传压缩文件
const { upload_url, file_id } = await getUploadUrl(compressedFile, { const { upload_url, file_id } = await getUploadUrl(compressedFile, {
originalUri: asset.uri, originalUri: asset.uri,
@ -256,23 +243,20 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
await uploadFile(compressedFile, upload_url); await uploadFile(compressedFile, upload_url);
await confirmUpload(file_id); await confirmUpload(file_id);
console.log(`Successfully uploaded compressed: ${filename}`);
return { success: true, file_id }; return { success: true, file_id };
} catch (error) { } catch (error) {
console.error('Error uploading compressed file:', error);
return { success: false, error }; return { success: false, error };
} }
}; };
// 同时上传原始文件和压缩文件 // 同时上传原始文件和压缩文件
console.log("Asset info:", asset);
const [originalResult, compressedResult] = await Promise.all([ const [originalResult, compressedResult] = await Promise.all([
uploadOriginalFile(), uploadOriginalFile(),
uploadCompressedFile() uploadCompressedFile()
]); ]);
console.log("originalResult", originalResult);
console.log("compressedResult", compressedResult); // 添加素材
addMaterial(originalResult.file_id, compressedResult.file_id) addMaterial(originalResult.file_id, compressedResult?.file_id || '')
return { return {
originalSuccess: originalResult.success, originalSuccess: originalResult.success,
compressedSuccess: compressedResult.success, compressedSuccess: compressedResult.success,
@ -282,7 +266,6 @@ const processMediaUpload = async (asset: MediaLibrary.Asset) => {
} }
}; };
} catch (error: any) { } catch (error: any) {
console.error('Error in processMediaUpload:', error);
if (error.message === 'No media library permission') { if (error.message === 'No media library permission') {
throw error; throw error;
} }
@ -310,10 +293,8 @@ export const registerBackgroundUploadTask = async () => {
startOnBoot: true, // 系统启动后自动运行 startOnBoot: true, // 系统启动后自动运行
}); });
console.log('Background upload task registered');
return true; return true;
} catch (error) { } catch (error) {
console.error('Error registering background task:', error);
return false; return false;
} }
}; };
@ -321,12 +302,9 @@ export const registerBackgroundUploadTask = async () => {
// 定义后台任务 // 定义后台任务
TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => { TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
try { try {
console.log('Background upload task started');
// 检查并请求媒体库权限 // 检查并请求媒体库权限
const { hasPermission } = await checkMediaLibraryPermission(); const { hasPermission } = await checkMediaLibraryPermission();
if (!hasPermission) { if (!hasPermission) {
console.log('Media library permission not granted');
return BackgroundFetch.BackgroundFetchResult.NoData; return BackgroundFetch.BackgroundFetchResult.NoData;
} }
@ -335,15 +313,11 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const media = await getMediaByDateRange(yesterday, now); const media = await getMediaByDateRange(yesterday, now);
console.log("media", media);
if (media.length === 0) { if (media.length === 0) {
console.log('No media found in the specified time range');
return BackgroundFetch.BackgroundFetchResult.NoData; return BackgroundFetch.BackgroundFetchResult.NoData;
} }
console.log(`Found ${media.length} media items to process`);
// 创建并发限制器 // 创建并发限制器
const limit = pLimit(CONCURRENCY_LIMIT); const limit = pLimit(CONCURRENCY_LIMIT);
@ -354,7 +328,6 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
await processMediaUpload(item); await processMediaUpload(item);
return { success: true, id: item.id }; return { success: true, id: item.id };
} catch (error: any) { } catch (error: any) {
console.error(`Error processing media ${item.id}:`, error);
if (error.message === 'No media library permission') { if (error.message === 'No media library permission') {
throw error; // 权限错误直接抛出 throw error; // 权限错误直接抛出
} }
@ -372,8 +345,6 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
).length; ).length;
const failed = results.length - succeeded; const failed = results.length - succeeded;
console.log(`Background upload task completed. Success: ${succeeded}, Failed: ${failed}`);
// 如果有权限错误,返回失败 // 如果有权限错误,返回失败
const hasPermissionError = results.some(r => const hasPermissionError = results.some(r =>
r.status === 'rejected' || r.status === 'rejected' ||
@ -388,7 +359,6 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
BackgroundFetch.BackgroundFetchResult.NewData : BackgroundFetch.BackgroundFetchResult.NewData :
BackgroundFetch.BackgroundFetchResult.NoData; BackgroundFetch.BackgroundFetchResult.NoData;
} catch (error) { } catch (error) {
console.error('Error in background upload task:', error);
return BackgroundFetch.BackgroundFetchResult.Failed; return BackgroundFetch.BackgroundFetchResult.Failed;
} }
}); });
@ -396,12 +366,7 @@ TaskManager.defineTask(BACKGROUND_UPLOAD_TASK, async () => {
// 手动触发上传 // 手动触发上传
export const triggerManualUpload = async (startDate: Date, endDate: Date) => { export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
try { try {
console.log('Starting manual upload...');
console.log("startDate", startDate);
console.log("endDate", endDate);
const media = await getMediaByDateRange(startDate, endDate); const media = await getMediaByDateRange(startDate, endDate);
console.log("media", media);
if (media.length === 0) { if (media.length === 0) {
Alert.alert('提示', '在指定时间范围内未找到媒体文件'); Alert.alert('提示', '在指定时间范围内未找到媒体文件');
return []; return [];
@ -419,7 +384,6 @@ export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
fileIds: result.fileIds, fileIds: result.fileIds,
}); });
} catch (error) { } catch (error) {
console.error(`Error uploading ${item.filename}:`, error);
results.push({ results.push({
id: item.id, id: item.id,
originalSuccess: false, originalSuccess: false,
@ -435,7 +399,6 @@ export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
return results; return results;
} catch (error) { } catch (error) {
console.error('Error in manual upload:', error);
Alert.alert('错误', '上传过程中出现错误'); Alert.alert('错误', '上传过程中出现错误');
throw error; throw error;
} }

View File

@ -191,6 +191,7 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
}; };
const uploadWithProgress = async (file: File, metadata: any): Promise<ConfirmUpload> => { const uploadWithProgress = async (file: File, metadata: any): Promise<ConfirmUpload> => {
let timeoutId: number let timeoutId: number
console.log("uploadWithProgress", metadata);
try { try {
console.log("Starting upload for file:", file.name, "size:", file.size, "type:", file.type); console.log("Starting upload for file:", file.name, "size:", file.size, "type:", file.type);
@ -201,7 +202,7 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
throw new Error(`文件大小超过限制 (${(MAX_FILE_SIZE / 1024 / 1024).toFixed(1)}MB)`); throw new Error(`文件大小超过限制 (${(MAX_FILE_SIZE / 1024 / 1024).toFixed(1)}MB)`);
} }
const uploadUrlData = await getUploadUrl(file, {}); const uploadUrlData = await getUploadUrl(file, { ...metadata, GPSVersionID: undefined });
console.log("Got upload URL for:", file.name); console.log("Got upload URL for:", file.name);
return new Promise<ConfirmUpload>((resolve, reject) => { return new Promise<ConfirmUpload>((resolve, reject) => {
@ -267,6 +268,8 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
// 处理单个资源 // 处理单个资源
const processSingleAsset = async (asset: ImagePicker.ImagePickerAsset): Promise<UploadResult | null> => { const processSingleAsset = async (asset: ImagePicker.ImagePickerAsset): Promise<UploadResult | null> => {
console.log("asset111111", asset);
const fileId = `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const fileId = `file_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const isVideo = asset.type === 'video'; const isVideo = asset.type === 'video';
const uploadResults: UploadResult = { const uploadResults: UploadResult = {