166 lines
4.5 KiB
TypeScript
166 lines
4.5 KiB
TypeScript
import { useState } from 'react';
|
||
import * as tus from 'tus-js-client';
|
||
|
||
interface UploadResult {
|
||
compressedUrl: string;
|
||
url: string;
|
||
fileId: string;
|
||
fileName: string;
|
||
}
|
||
|
||
export function useTusUpload() {
|
||
const [uploadProgress, setUploadProgress] = useState<number>(0);
|
||
const [isUploading, setIsUploading] = useState<boolean>(false);
|
||
const [uploadError, setUploadError] = useState<string | null>(null);
|
||
|
||
// 获取服务器配置
|
||
const getServerUrl = () => {
|
||
const ip = process.env.NEXT_PUBLIC_SERVER_IP || 'http://localhost';
|
||
const port = process.env.NEXT_PUBLIC_SERVER_PORT || '3000';
|
||
return `${ip}:${port}`;
|
||
};
|
||
|
||
// 文件上传函数
|
||
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],
|
||
metadata: {
|
||
filename: file.name,
|
||
filetype: file.type,
|
||
},
|
||
onError: (error) => {
|
||
console.error('Upload failed:', error);
|
||
const errorMessage = error.message || 'Upload failed';
|
||
setUploadError(errorMessage);
|
||
setIsUploading(false);
|
||
onError?.(errorMessage);
|
||
reject(new Error(errorMessage));
|
||
},
|
||
onProgress: (bytesUploaded, bytesTotal) => {
|
||
const percentage = Math.round((bytesUploaded / bytesTotal) * 100);
|
||
setUploadProgress(percentage);
|
||
},
|
||
onSuccess: () => {
|
||
console.log('Upload completed successfully');
|
||
setIsUploading(false);
|
||
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);
|
||
},
|
||
});
|
||
|
||
// 开始上传
|
||
upload.start();
|
||
});
|
||
};
|
||
|
||
// 根据 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;
|
||
} catch (error) {
|
||
console.error('Failed to get upload status:', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
return {
|
||
uploadProgress,
|
||
isUploading,
|
||
uploadError,
|
||
handleFileUpload,
|
||
getFileUrlByFileId,
|
||
getFileInfo,
|
||
getUploadStatus,
|
||
serverUrl: getServerUrl(),
|
||
};
|
||
}
|