bw-expo-app/lib/api/upload.ts

150 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { storage } from '../storage';
export interface UploadResponse {
success: boolean;
data?: {
url: string;
filename: string;
size: number;
mimeType: string;
};
message?: string;
}
async function getAuthToken(): Promise<string> {
return (await storage.getItem(`bestaibest.better-auth.session_token`)) || '';
}
/**
* 上传文件到服务器
* @param uri 本地文件 URI可以是 blob: 或 file:// 格式)
* @param type 文件类型image 或 video
*/
export async function uploadFile(uri: string, type: 'image' | 'video'): Promise<UploadResponse> {
try {
// 如果已经是 https URL直接返回
if (uri.startsWith('https://') || uri.startsWith('http://')) {
return {
success: true,
data: {
url: uri,
filename: uri.split('/').pop() || 'file',
size: 0,
mimeType: type === 'image' ? 'image/jpeg' : 'video/mp4',
},
};
}
// 先获取 blob 以确定实际的 MIME 类型
const response = await fetch(uri);
const blob = await response.blob();
// 从 blob 获取实际的 MIME 类型
let mimeType = blob.type;
// 如果 blob 没有 type则根据参数推断
if (!mimeType || mimeType === 'application/octet-stream') {
mimeType = type === 'image' ? 'image/jpeg' : 'video/mp4';
}
// 根据 MIME 类型确定文件扩展名
let extension: string;
if (mimeType.startsWith('image/')) {
const imageExtensions: Record<string, string> = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
};
extension = imageExtensions[mimeType] || 'jpg';
} else {
const videoExtensions: Record<string, string> = {
'video/mp4': 'mp4',
'video/webm': 'webm',
'video/avi': 'avi',
'video/quicktime': 'mov',
};
extension = videoExtensions[mimeType] || 'mp4';
}
// 生成文件名
const filename = `${type}_${Date.now()}.${extension}`;
// 创建 File 对象
const file = new File([blob], filename, { type: mimeType });
// 创建 FormData
const formData = new FormData();
formData.append('file', file);
// 获取认证 token
const token = await getAuthToken();
// 上传文件(不要手动设置 Content-Type让浏览器自动添加 boundary
const uploadResponse = await fetch(`https://api.mixvideo.bowong.cc/api/file/upload/s3`, {
method: 'POST',
headers: token ? {
'Authorization': `Bearer ${token}`,
} : {},
body: formData,
});
if (!uploadResponse.ok) {
const errorData = await uploadResponse.json();
const errorMessage = errorData?.error?.message || errorData?.message || uploadResponse.statusText;
throw new Error(`Upload failed: ${errorMessage}`);
}
const result = await uploadResponse.json();
// 支持多种返回格式:
// 1. { status: true, msg: "...", data: "url" } - 实际 S3 上传格式
// 2. { success: true, data: { url, ... } }
// 3. { data: { status: true, data: "url" } }
if (result.status === true && result.data) {
// 格式 1实际 S3 格式)
return {
success: true,
data: {
url: result.data,
filename: filename,
size: file.size,
mimeType: mimeType,
},
};
} else if (result.data?.status && result.data?.data) {
// 格式 3
return {
success: true,
data: {
url: result.data.data,
filename: filename,
size: file.size,
mimeType: mimeType,
},
};
} else if (result.success && result.data) {
// 格式 2
return result;
} else {
throw new Error(result.error?.message || result.msg || result.message || 'Upload failed');
}
} catch (error) {
console.error('Failed to upload file:', error);
return {
success: false,
message: error instanceof Error ? error.message : '上传失败,请稍后重试',
};
}
}
/**
* 批量上传文件
*/
export async function uploadFiles(
files: Array<{ uri: string; type: 'image' | 'video' }>
): Promise<UploadResponse[]> {
const uploadPromises = files.map(file => uploadFile(file.uri, file.type));
return Promise.all(uploadPromises);
}