UNPKG

mira-app-core

Version:

Core library for Mira TypeScript project - provides base functionality without auto-execution

185 lines (166 loc) 8.25 kB
import { Router, Request, Response } from 'express'; import { MiraBackend } from '../MiraBackend'; import * as fs from 'fs'; import * as path from 'path'; import multer from 'multer'; export class FileRoutes { private router: Router; private backend: MiraBackend; private upload!: multer.Multer; constructor(backend: MiraBackend) { this.backend = backend; this.router = Router(); this.setupUpload(); this.setupRoutes(); } private setupUpload(): void { // 配置multer文件上传 this.upload = multer({ storage: multer.diskStorage({ destination: (req, file, cb) => { const tempDir = path.join(this.backend.dataPath, 'temp'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } cb(null, tempDir); }, filename: (req, file, cb) => { // 处理中文名,确保文件名为utf8编码 const originalName = Buffer.from(file.originalname, 'latin1').toString('utf8'); const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, uniqueSuffix + path.extname(originalName)); } }) }); } private setupRoutes(): void { // 上传文件到资源库 this.router.post('/upload', this.upload.array('files'), async (req: Request, res: Response) => { const { libraryId, sourcePath } = req.body; // sourcePath是用户的本地文件位置,用来验证是否上传成功 const clientId = req.body.clientId || null; const fields = req.body.fields ? JSON.parse(req.body.fields) : null; const payload = req.body.payload ? JSON.parse(req.body.payload) : null; const obj = this.backend.libraries.get(libraryId); if (!obj) return res.status(404).send('Library not found'); // 解析上传的文件 const files = req.files as Express.Multer.File[]; if (!files || !files.length) return res.status(400).send('No files uploaded.'); try { const results = []; for (const file of files) { try { // 生成唯一文件名并保存文件 const originalName = Buffer.from(file.originalname, 'latin1').toString('utf8'); const { tags, folder_id } = payload.data || {} const fileData = { name: req.body.name || originalName, tags: JSON.stringify(tags || []), folder_id: folder_id || null, }; const result = await obj.libraryService.createFileFromPath(file.path, fileData, { importType: 'move' }); // 使用move上传完成后自动删除临时文件 results.push({ success: true, file: file.path, result }); // 发布公告 // 发送WebSocket事件(如果可用) if (this.backend.webSocketServer) { this.backend.webSocketServer.broadcastPluginEvent('file::created', { message: { type: 'file', action: 'create', fields, payload }, result, libraryId }); if (clientId) { const ws = this.backend.webSocketServer?.getWsClientById(libraryId, clientId); ws && this.backend.webSocketServer?.sendToWebsocket(ws, { eventName: 'file::uploaded', data: { path: sourcePath } }); this.backend.webSocketServer?.broadcastLibraryEvent(libraryId, 'file::created', { ...result, libraryId }); } } } catch (error) { console.error(`Error processing file ${file.originalname}:`, error); results.push({ success: false, file: file.path, error: error instanceof Error ? error.message : String(error) }); } } res.send({ results }); } catch (error) { console.error('Error uploading files:', error); res.status(500).send('Internal server error while processing the upload.'); } }); // 获取文件缩略图 this.router.get('/thumb/:libraryId/:id', async (req: Request, res: Response) => { try { const ret = await this.parseLibraryItem(req, res); if (ret) { const thumbPath = await ret.library.getItemThumbPath(ret.item, { isNetworkImage: false }); if (!fs.existsSync(thumbPath)) return res.status(404).send('Thumbnail not found'); res.setHeader('Content-Type', 'image/png'); fs.createReadStream(thumbPath).pipe(res); } } catch (err) { console.error('Error serving thumbnail:', err); res.status(500).send('Internal server error'); } }); // 获取文件内容 this.router.get('/file/:libraryId/:id', async (req: Request, res: Response) => { const ret = await this.parseLibraryItem(req, res); if (ret) { const filePath = await ret.library.getItemFilePath(ret.item); if (!filePath || !fs.existsSync(filePath)) { return res.status(404).send('File not found'); } const fileExt = path.extname(filePath).toLowerCase(); const contentType = this.getContentType(fileExt); res.setHeader('Content-Type', contentType); fs.createReadStream(filePath).pipe(res); } }); } private async parseLibraryItem(req: Request, res: Response): Promise<{ library: any, item: any } | void> { const { libraryId, id } = req.params; const obj = this.backend.libraries.get(libraryId); if (!obj) { res.status(404).send('Library not found'); return; } const item = await obj.libraryService.getFile(parseInt(id)); if (!item) { res.status(404).send('Item not found'); return; } return { library: obj.libraryService, item }; } private getContentType(ext: string): string { const mimeTypes: Record<string, string> = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.pdf': 'application/pdf', '.txt': 'text/plain', '.html': 'text/html', '.json': 'application/json', '.mp4': 'video/mp4', '.mp3': 'audio/mpeg', '.zip': 'application/zip', '.doc': 'application/msword', '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.xls': 'application/vnd.ms-excel', '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.ppt': 'application/vnd.ms-powerpoint', '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }; return mimeTypes[ext] || 'application/octet-stream'; } public getRouter(): Router { return this.router; } }