fenghuo/apps/web/hooks/useTusUpload.ts

166 lines
4.5 KiB
TypeScript
Raw Normal View History

2025-05-28 20:00:36 +08:00
import { useState } from 'react';
import * as tus from 'tus-js-client';
2025-05-28 08:23:15 +08:00
interface UploadResult {
compressedUrl: string;
url: string;
fileId: string;
fileName: string;
}
export function useTusUpload() {
2025-05-28 20:00:36 +08:00
const [uploadProgress, setUploadProgress] = useState<number>(0);
const [isUploading, setIsUploading] = useState<boolean>(false);
2025-05-28 08:23:15 +08:00
const [uploadError, setUploadError] = useState<string | null>(null);
2025-05-28 20:00:36 +08:00
// 获取服务器配置
const getServerUrl = () => {
const ip = process.env.NEXT_PUBLIC_SERVER_IP || 'http://localhost';
const port = process.env.NEXT_PUBLIC_SERVER_PORT || '3000';
return `${ip}:${port}`;
2025-05-28 08:23:15 +08:00
};
2025-05-28 20:00:36 +08:00
// 文件上传函数
const handleFileUpload = (
file: File,
onSuccess?: (result: UploadResult) => void,
onError?: (error: string) => void,
): Promise<UploadResult> => {
return new Promise((resolve, reject) => {
setIsUploading(true);
setUploadProgress(0);
setUploadError(null);
const serverUrl = getServerUrl();
const uploadUrl = `${serverUrl}/upload`;
const upload = new tus.Upload(file, {
endpoint: uploadUrl,
retryDelays: [0, 3000, 5000, 10000, 20000],
2025-05-28 08:23:15 +08:00
metadata: {
2025-05-28 20:00:36 +08:00
filename: file.name,
filetype: file.type,
2025-05-28 08:23:15 +08:00
},
2025-05-28 20:00:36 +08:00
onError: (error) => {
console.error('Upload failed:', error);
const errorMessage = error.message || 'Upload failed';
setUploadError(errorMessage);
setIsUploading(false);
onError?.(errorMessage);
reject(new Error(errorMessage));
2025-05-28 08:23:15 +08:00
},
2025-05-28 20:00:36 +08:00
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = Math.round((bytesUploaded / bytesTotal) * 100);
setUploadProgress(percentage);
2025-05-28 08:23:15 +08:00
},
2025-05-28 20:00:36 +08:00
onSuccess: () => {
console.log('Upload completed successfully');
2025-05-28 08:23:15 +08:00
setIsUploading(false);
2025-05-28 20:00:36 +08:00
setUploadProgress(100);
// 从上传 URL 中提取目录格式的 fileId
const uploadUrl = upload.url;
if (!uploadUrl) {
const error = 'Failed to get upload URL';
setUploadError(error);
onError?.(error);
reject(new Error(error));
return;
}
// 提取完整的上传ID然后移除文件名部分得到目录路径
const fullUploadId = uploadUrl.replace(/^.*\/upload\//, '');
const fileId = fullUploadId.replace(/\/[^/]+$/, '');
console.log('Full upload ID:', fullUploadId);
console.log('Extracted fileId (directory):', fileId);
const result: UploadResult = {
fileId,
fileName: file.name,
url: getFileUrlByFileId(fileId),
compressedUrl: getFileUrlByFileId(fileId), // 对于简单实现,压缩版本和原版本相同
};
onSuccess?.(result);
resolve(result);
2025-05-28 08:23:15 +08:00
},
});
2025-05-28 20:00:36 +08:00
// 开始上传
2025-05-28 08:23:15 +08:00
upload.start();
2025-05-28 20:00:36 +08:00
});
};
// 根据 fileId 获取文件访问链接
const getFileUrlByFileId = (fileId: string): string => {
const serverUrl = getServerUrl();
// 对fileId进行URL编码以处理其中的斜杠
const encodedFileId = encodeURIComponent(fileId);
return `${serverUrl}/download/${encodedFileId}`;
};
// 检查文件是否存在并获取详细信息
const getFileInfo = async (fileId: string) => {
try {
const serverUrl = getServerUrl();
// 对fileId进行URL编码以处理其中的斜杠
const encodedFileId = encodeURIComponent(fileId);
const response = await fetch(`${serverUrl}/api/storage/resource/${encodedFileId}`);
const data = await response.json();
if (data.status === 'UPLOADED' && data.resource) {
return {
...data.resource,
url: getFileUrlByFileId(fileId),
};
}
console.log('File info response:', data);
return null;
} catch (error) {
console.error('Failed to get file info:', error);
return null;
}
};
// 获取上传状态
const getUploadStatus = async (fileId: string) => {
try {
const serverUrl = getServerUrl();
const response = await fetch(`${serverUrl}/upload/${fileId}`, {
method: 'HEAD',
});
if (response.status === 200) {
const uploadLength = response.headers.get('Upload-Length');
const uploadOffset = response.headers.get('Upload-Offset');
return {
isComplete: uploadLength === uploadOffset,
progress:
uploadLength && uploadOffset ? Math.round((parseInt(uploadOffset) / parseInt(uploadLength)) * 100) : 0,
uploadLength: uploadLength ? parseInt(uploadLength) : 0,
uploadOffset: uploadOffset ? parseInt(uploadOffset) : 0,
};
}
return null;
2025-05-28 08:23:15 +08:00
} catch (error) {
2025-05-28 20:00:36 +08:00
console.error('Failed to get upload status:', error);
return null;
2025-05-28 08:23:15 +08:00
}
};
return {
uploadProgress,
isUploading,
uploadError,
handleFileUpload,
2025-05-28 20:00:36 +08:00
getFileUrlByFileId,
getFileInfo,
getUploadStatus,
serverUrl: getServerUrl(),
2025-05-28 08:23:15 +08:00
};
}