import { Server, Upload } from '@repo/tus'; import { prisma } from '@repo/db'; import { getFilenameWithoutExt } from '../utils/file'; import { nanoid } from 'nanoid'; import { slugify } from 'transliteration'; import { StorageManager } from './storage.adapter'; const FILE_UPLOAD_CONFIG = { maxSizeBytes: 20_000_000_000, // 20GB }; export enum QueueJobType { UPDATE_STATS = 'update_stats', FILE_PROCESS = 'file_process', UPDATE_POST_VISIT_COUNT = 'updatePostVisitCount', UPDATE_POST_STATE = 'updatePostState', } export enum ResourceStatus { UPLOADING = 'UPLOADING', UPLOADED = 'UPLOADED', PROCESS_PENDING = 'PROCESS_PENDING', PROCESSING = 'PROCESSING', PROCESSED = 'PROCESSED', PROCESS_FAILED = 'PROCESS_FAILED', } // 全局 TUS 服务器实例 let tusServer: Server | null = null; function getFileId(uploadId: string) { return uploadId.replace(/\/[^/]+$/, ''); } async function handleUploadCreate(req: any, res: any, upload: Upload, url: string) { try { const fileId = getFileId(upload.id); const storageManager = StorageManager.getInstance(); await prisma.resource.create({ data: { title: getFilenameWithoutExt(upload.metadata?.filename || 'untitled'), fileId, // 移除最后的文件名 url: upload.id, meta: upload.metadata, status: ResourceStatus.UPLOADING, storageType: storageManager.getStorageType(), // 记录存储类型 }, }); console.log(`Resource created for ${upload.id} using ${storageManager.getStorageType()} storage`); } catch (error) { console.error('Failed to create resource during upload', error); } } async function handleUploadFinish(req: any, res: any, upload: Upload) { try { const resource = await prisma.resource.update({ where: { fileId: getFileId(upload.id) }, data: { status: ResourceStatus.UPLOADED }, }); // TODO: 这里可以添加队列处理逻辑 // fileQueue.add(QueueJobType.FILE_PROCESS, { resource }, { jobId: resource.id }); console.log(`Upload finished ${resource.url} using ${StorageManager.getInstance().getStorageType()} storage`); } catch (error) { console.error('Failed to update resource after upload', error); } } function initializeTusServer() { if (tusServer) { return tusServer; } // 获取存储管理器实例 const storageManager = StorageManager.getInstance(); const dataStore = storageManager.getDataStore(); tusServer = new Server({ namingFunction(req, metadata) { const safeFilename = slugify(metadata?.filename || 'untitled'); const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const uniqueId = nanoid(10); return `${year}/${month}/${day}/${uniqueId}/${safeFilename}`; }, path: '/upload', datastore: dataStore, // 使用存储适配器 maxSize: FILE_UPLOAD_CONFIG.maxSizeBytes, postReceiveInterval: 1000, getFileIdFromRequest: (req, lastPath) => { const match = req.url?.match(/\/upload\/(.+)/); return match ? match[1] : lastPath; }, }); // 设置事件处理器 tusServer.on('POST_CREATE', handleUploadCreate); tusServer.on('POST_FINISH', handleUploadFinish); console.log(`TUS server initialized with ${storageManager.getStorageType()} storage`); return tusServer; } export function getTusServer() { return initializeTusServer(); } export async function handleTusRequest(req: any, res: any) { const server = getTusServer(); return server.handle(req, res); } export async function cleanupExpiredUploads() { try { const storageManager = StorageManager.getInstance(); // 获取过期时间配置,如果设置为 0 则不自动清理 const expirationPeriod: number = 24 * 60 * 60 * 1000; // Delete incomplete uploads older than expiration period const deletedResources = await prisma.resource.deleteMany({ where: { createdAt: { lt: new Date(Date.now() - expirationPeriod), }, status: ResourceStatus.UPLOADING, }, }); const server = getTusServer(); const expiredUploadCount = await server.cleanUpExpiredUploads(); console.log( `Cleanup complete: ${deletedResources.count} resources and ${expiredUploadCount} uploads removed from ${storageManager.getStorageType()} storage`, ); return { deletedResources: deletedResources.count, expiredUploads: expiredUploadCount }; } catch (error) { console.error('Expired uploads cleanup failed', error); throw error; } } // 获取存储信息 export function getStorageInfo() { const storageManager = StorageManager.getInstance(); return storageManager.getStorageInfo(); }