feat: 并发

This commit is contained in:
jinyaqiu 2025-07-15 20:41:43 +08:00
parent 036a7c7fa1
commit 19ed3bba52
3 changed files with 120 additions and 76 deletions

View File

@ -71,7 +71,7 @@ export default function OwnerPage() {
{/* 资源数据 */}
<View style={styles.resourceContainer}>
<ResourceComponent title={t("generalSetting.usedStorage", { ns: "personal" })} subtitle={`${countData?.counter?.total_count?.video_count || 0}videos/${countData?.counter?.total_count?.photo_count || 0}photos`} data={{ all: countData.total_bytes, used: countData.used_bytes }} icon={<UsedStorageSvg />} style={{ flex: 1 }} isFormatBytes={true} />
<ResourceComponent title={t("generalSetting.usedStorage", { ns: "personal" })} subtitle={`${countData?.counter?.total_count?.video_count || 0}videos/${countData?.counter?.total_count?.photo_count || 0}photos`} data={{ all: userInfoDetails.total_bytes, used: countData.used_bytes }} icon={<UsedStorageSvg />} style={{ flex: 1 }} isFormatBytes={true} />
<ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} style={{ flex: 1 }} />
</View>
{/* 数据统计 */}

View File

@ -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<File> => {
@ -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) {

View File

@ -57,7 +57,7 @@ export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
{userInfo?.user_info?.avatar_file_url
?
<Image
source={{ uri: "http://cdn.fairclip.cn/files/7348942720074911745/original_1752124481039_198d7b9a428c67f2bfd87ec128daad1b.jpg" }}
source={{ uri: userInfo?.user_info?.avatar_file_url }}
style={{ width: 80, height: 80, borderRadius: 40 }}
/>
: