feat: 并发
This commit is contained in:
parent
036a7c7fa1
commit
19ed3bba52
@ -71,7 +71,7 @@ export default function OwnerPage() {
|
|||||||
|
|
||||||
{/* 资源数据 */}
|
{/* 资源数据 */}
|
||||||
<View style={styles.resourceContainer}>
|
<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 }} />
|
<ResourceComponent title={t("generalSetting.remainingPoints", { ns: "personal" })} data={{ all: userInfoDetails.total_points, used: userInfoDetails.remain_points }} icon={<PointsSvg />} style={{ flex: 1 }} />
|
||||||
</View>
|
</View>
|
||||||
{/* 数据统计 */}
|
{/* 数据统计 */}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import * as FileSystem from 'expo-file-system';
|
|||||||
import * as ImageManipulator from 'expo-image-manipulator';
|
import * as ImageManipulator from 'expo-image-manipulator';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import * as TaskManager from 'expo-task-manager';
|
import * as TaskManager from 'expo-task-manager';
|
||||||
|
import pLimit from 'p-limit';
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import { transformData } from '../utils/objectFlat';
|
import { transformData } from '../utils/objectFlat';
|
||||||
|
|
||||||
@ -14,6 +15,30 @@ type ExtendedAsset = MediaLibrary.Asset & {
|
|||||||
const BACKGROUND_UPLOAD_TASK = 'background-upload-task';
|
const BACKGROUND_UPLOAD_TASK = 'background-upload-task';
|
||||||
// 设置最大并发数
|
// 设置最大并发数
|
||||||
const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件
|
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 图片转化
|
// 将 HEIC 图片转化
|
||||||
const convertHeicToJpeg = async (uri: string): Promise<File> => {
|
const convertHeicToJpeg = async (uri: string): Promise<File> => {
|
||||||
@ -274,42 +299,67 @@ const processMediaUpload = async (asset: ExtendedAsset) => {
|
|||||||
const uploadOriginalFile = async () => {
|
const uploadOriginalFile = async () => {
|
||||||
try {
|
try {
|
||||||
let fileToUpload: File;
|
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 ||
|
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 格式
|
// 处理 HEIC 格式
|
||||||
if (filename.toLowerCase().endsWith('.heic') || filename.toLowerCase().endsWith('.heif')) {
|
if (filename.toLowerCase().endsWith('.heic') || filename.toLowerCase().endsWith('.heif')) {
|
||||||
try {
|
fileToUpload = await convertHeicToJpeg(asset.uri);
|
||||||
fileToUpload = await convertHeicToJpeg(asset.uri);
|
filename = filename.replace(/\.(heic|heif)$/i, '.jpg');
|
||||||
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' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} 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();
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
// 创建文件对象
|
||||||
fileToUpload = new File([blob], filename, { type: mimeType });
|
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, {
|
const { upload_url, file_id } = await getUploadUrl(fileToUpload, {
|
||||||
originalUri: asset.uri,
|
originalUri: asset.uri,
|
||||||
creationTime: asset.creationTime,
|
creationTime: asset.creationTime,
|
||||||
@ -319,26 +369,31 @@ const processMediaUpload = async (asset: ExtendedAsset) => {
|
|||||||
GPSVersionID: undefined
|
GPSVersionID: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
await uploadFile(fileToUpload, upload_url);
|
await uploadFile(fileToUpload, upload_url);
|
||||||
await confirmUpload(file_id);
|
await confirmUpload(file_id);
|
||||||
|
|
||||||
return { success: true, file_id };
|
console.log('文件上传成功:', {
|
||||||
} catch (error: unknown) {
|
fileId: file_id,
|
||||||
if (error instanceof Error) {
|
filename: fileToUpload.name,
|
||||||
console.error('Error in uploadOriginalFile:', {
|
type: fileToUpload.type
|
||||||
message: error.message,
|
});
|
||||||
name: error.name,
|
|
||||||
stack: error.stack,
|
return {
|
||||||
asset: {
|
success: true,
|
||||||
id: asset.id,
|
file_id,
|
||||||
uri: asset.uri,
|
filename: fileToUpload.name
|
||||||
filename: asset.filename
|
};
|
||||||
}
|
|
||||||
});
|
} catch (error: any) {
|
||||||
} else {
|
const errorMessage = error instanceof Error ? error.message : '未知错误';
|
||||||
console.error('An unknown error occurred:', error);
|
console.error('上传原始文件时出错:', {
|
||||||
}
|
error: errorMessage,
|
||||||
throw error;
|
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 photos = media.filter(item => item.mediaType === 'photo');
|
||||||
const videos = media.filter(item => item.mediaType === 'video');
|
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 {
|
try {
|
||||||
const result = await processMediaUpload(item);
|
const result = await processMediaUpload(item);
|
||||||
results.push({
|
results.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
...result
|
...result
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
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
|
|
||||||
});
|
|
||||||
results.push({
|
results.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
originalSuccess: false,
|
originalSuccess: false,
|
||||||
compressedSuccess: false,
|
compressedSuccess: false,
|
||||||
error: errorMessage
|
error: error.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 再处理所有视频
|
// 处理所有视频(带并发控制)
|
||||||
for (const item of videos) {
|
const processVideo = async (item: any) => {
|
||||||
try {
|
try {
|
||||||
const result = await processMediaUpload(item);
|
const result = await processMediaUpload(item);
|
||||||
results.push({
|
results.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
...result
|
...result
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: any) {
|
||||||
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
|
|
||||||
});
|
|
||||||
results.push({
|
results.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
originalSuccess: false,
|
originalSuccess: false,
|
||||||
compressedSuccess: 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;
|
return results;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export default function UserInfo({ userInfo }: { userInfo: UserInfoDetails }) {
|
|||||||
{userInfo?.user_info?.avatar_file_url
|
{userInfo?.user_info?.avatar_file_url
|
||||||
?
|
?
|
||||||
<Image
|
<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 }}
|
style={{ width: 80, height: 80, borderRadius: 40 }}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user