自制局域网文件传输工具

自制局域网文件传输工具其实就是自建一个 web 服务器 这也是最简单好跨平台的方法了 先看看基本需求 1 批量上传文件 2 图片文件预览 3 文件可以删除我们在这里使用 nodejs 的 Express 搭建 web 服务器 你问我什么用 js 因为我喜欢 server

欢迎大家来到IT世界,在知识的湖畔探索吧!

其实就是自建一个web服务器,这也是最简单好跨平台的方法了。

先看看基本需求:

1、批量上传文件

2、图片文件预览

3、文件可以删除

我们在这里使用nodejs的Express搭建web服务器,你问我什么用js?因为我喜欢。

server.js

import express from 'express'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import multer from 'multer'; import { fileTypeFromBuffer } from 'file-type'; import sharp from 'sharp'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const port = 3000; // 确保必要的目录存在 const uploadDir = 'uploads'; const dataDir = 'data'; const thumbnailDir = 'thumbnails'; const fileInfoPath = path.join(dataDir, 'files.json'); [uploadDir, dataDir, thumbnailDir].forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); // 初始化文件信息JSON if (!fs.existsSync(fileInfoPath)) { fs.writeFileSync(fileInfoPath, JSON.stringify([], null, 2)); } // 缩略图生成函数 async function generateThumbnail(filePath, filename) { try { const thumbnailPath = path.join(thumbnailDir, filename); // 如果缩略图已存在,直接返回 if (fs.existsSync(thumbnailPath)) { return thumbnailPath; } // 生成缩略图 await sharp(filePath) .resize(300, 225, { fit: 'cover', position: 'centre' }) .toFile(thumbnailPath); return thumbnailPath; } catch (error) { throw error; } } // 文件信息管理函数 function loadFileInfo() { try { const data = fs.readFileSync(fileInfoPath, 'utf8'); return JSON.parse(data); } catch (error) { return []; } } function saveFileInfo(fileInfo) { try { fs.writeFileSync(fileInfoPath, JSON.stringify(fileInfo, null, 2), 'utf8'); return true; } catch (error) { return false; } } function getUniqueFileName(originalName, fileInfo) { // 获取文件名和扩展名 const ext = path.extname(originalName); const nameWithoutExt = path.basename(originalName, ext); // 转义正则表达式特殊字符 const escapedNameWithoutExt = nameWithoutExt.replace(/[.*+?^${}()|[\]\\]/g, '\\'); // 检查是否存在同名文件 const existingFiles = fileInfo.filter(file => { const lowerOriginal = file.originalName.toLowerCase(); const lowerInput = originalName.toLowerCase(); const lowerNameWithoutExt = nameWithoutExt.toLowerCase(); return lowerOriginal === lowerInput || (lowerOriginal.startsWith(lowerNameWithoutExt + ' (') && lowerOriginal.endsWith(')' + ext.toLowerCase())); }); if (existingFiles.length === 0) { return originalName; } // 找到当前最大的序号 let maxNum = 0; const escapedExt = ext.replace(/[.*+?^${}()|[\]\\]/g, '\\'); const pattern = new RegExp(`^${escapedNameWithoutExt}\\s*\\((\\d+)\\)${escapedExt} 
   
    
     
     自制局域网文件传输工具 - 今日头条 
      
       
        
         
          
           
            
             
              
               
                
                 
                 
                  
                   
                    
                     
                      
                       
                        
                         
                          
                           
                            
                             
                              
                               
                                
                                 
                                  
                                   
                                    
                                     
                                      
                                       
                                        
                                         
                                          
                                           
                                             
                                             
                                             
                                               
                                               
                                               
                                               
                                               
                                               
                                               
                                               
                                                
                                                 
                                                
                                               
                                              
                                            
                                          
                                         
                                        
                                       
                                      
                                     
                                    
                                   
                                  
                                 
                                
                               
                              
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
   
 
, 'i'); existingFiles.forEach(file => { const match = file.originalName.match(pattern); if (match) { const num = parseInt(match[1], 10); if (num > maxNum) maxNum = num; } }); // 返回新的文件名 return `${nameWithoutExt} (${maxNum + 1})${ext}`; } function addFileInfo(originalName, newName, size, mimeType) { const fileInfo = loadFileInfo(); // 获取唯一的原始文件名 const uniqueOriginalName = getUniqueFileName(originalName, fileInfo); fileInfo.unshift({ // 新文件添加到列表开头 id: Date.now().toString(), originalName: uniqueOriginalName, newName, size, mimeType, uploadTime: new Date().toISOString(), lastModified: new Date().toISOString() }); return saveFileInfo(fileInfo); } function removeFileInfo(newName) { const fileInfo = loadFileInfo(); const updatedInfo = fileInfo.filter(file => file.newName !== newName); return saveFileInfo(updatedInfo); } function getFileByNewName(newName) { const fileInfo = loadFileInfo(); return fileInfo.find(file => file.newName === newName); } // 配置上传文件存储 const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadDir); }, filename: (req, file, cb) => { try { // 处理前端发送的编码文件名 let decodedName = file.originalname; // 尝试解码URI组件(前端可能已经编码) try { decodedName = decodeURIComponent(decodedName); } catch (e) { // 如果不是URI编码,继续使用原始名称 } // 确保UTF-8编码 if (Buffer.from(decodedName, 'latin1').toString('utf8') !== decodedName) { decodedName = Buffer.from(decodedName, 'latin1').toString('utf8'); } // 保存原始文件名到req对象,供后续使用 req.originalFileName = decodedName; // 获取文件扩展名 const ext = path.extname(decodedName); // 使用时间戳作为存储文件名 const timestamp = Date.now(); const newFileName = `${timestamp}${ext}`; cb(null, newFileName); } catch (error) { // 发生错误时使用时间戳作为文件名 const timestamp = Date.now(); cb(null, `${timestamp}.unknown`); } } }); const upload = multer({ storage: storage }); // 静态文件服务 app.use(express.static('public')); // 缩略图路由 app.get('/thumbnail/:filename', async (req, res) => { try { const decodedFilename = decodeURIComponent(req.params.filename); const filePath = path.join(uploadDir, decodedFilename); // 检查原始文件是否存在 if (!fs.existsSync(filePath)) { return res.status(404).send('文件不存在'); } // 检查文件类型 const fileBuffer = fs.readFileSync(filePath); const fileType = await fileTypeFromBuffer(fileBuffer); // 只处理图片文件 if (!fileType || !fileType.mime.startsWith('image/')) { return res.status(400).send('不支持的文件类型'); } try { // 生成缩略图 const thumbnailPath = await generateThumbnail(filePath, decodedFilename); res.sendFile(thumbnailPath, { root: process.cwd() }); } catch (error) { // 如果生成缩略图失败,返回原图 res.sendFile(filePath, { root: process.cwd() }); } } catch (error) { res.status(500).send('处理缩略图时出错'); } }); // 文件下载路由 - 处理文件名编码 app.get('/download/:filename', (req, res) => { try { // 解码文件名 const decodedFilename = decodeURIComponent(req.params.filename); const filePath = path.join(uploadDir, decodedFilename); // 检查文件是否存在 if (!fs.existsSync(filePath)) { return res.status(404).send('文件不存在'); } // 设置Content-Disposition头,确保文件名正确编码 res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(decodedFilename)}`); // 发送文件 res.sendFile(filePath, { root: process.cwd() }); } catch (error) { res.status(500).send('文件下载失败'); } }); // 获取文件列表(按上传时间降序排列) app.get('/files', (req, res) => { try { const fileInfo = loadFileInfo(); // 验证文件是否真实存在 const validFiles = fileInfo.filter(file => { const filePath = path.join(uploadDir, file.newName); return fs.existsSync(filePath); }); // 如果有文件不存在,更新JSON if (validFiles.length !== fileInfo.length) { saveFileInfo(validFiles); } // 返回文件信息 res.json(validFiles.map(file => ({ id: file.id, originalName: file.originalName, newName: file.newName, size: file.size, uploadTime: file.uploadTime, mimeType: file.mimeType }))); } catch (err) { res.status(500).json([]); } }); // 获取文件信息 app.get('/file-info/:filename', (req, res) => { try { // 解码文件名 const decodedFilename = decodeURIComponent(req.params.filename); const fileInfo = getFileByNewName(decodedFilename); if (!fileInfo) { return res.status(404).json({ error: '文件信息不存在' }); } const filePath = path.join(uploadDir, decodedFilename); // 检查文件是否实际存在 fs.stat(filePath, (err, stats) => { if (err) { console.error('获取文件信息错误:', err); // 文件不存在,从JSON中删除记录 removeFileInfo(decodedFilename); return res.status(404).json({ error: '文件不存在' }); } res.json({ id: fileInfo.id, originalName: fileInfo.originalName, newName: fileInfo.newName, size: fileInfo.size, uploadTime: fileInfo.uploadTime, mimeType: fileInfo.mimeType, modified: stats.mtime }); }); } catch (error) { console.error('文件信息获取错误:', error); res.status(400).json({ error: '无效的文件名' }); } }); // 普通文件上传 app.post('/upload', upload.single('file'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ error: '没有上传文件' }); } // 检测文件类型 const buffer = fs.readFileSync(req.file.path); const fileType = await fileTypeFromBuffer(buffer); const mimeType = fileType ? fileType.mime : 'application/octet-stream'; // 如果是图片,预先生成缩略图 if (mimeType.startsWith('image/')) { try { await generateThumbnail(req.file.path, req.file.filename); } catch (error) { // 继续处理,即使缩略图生成失败 } } // 保存文件信息到JSON,使用处理后的文件名 const success = addFileInfo( req.originalFileName || req.file.originalname, req.file.filename, req.file.size, mimeType ); if (!success) { // 如果保存文件信息失败,删除上传的文件和可能存在的缩略图 fs.unlinkSync(req.file.path); const thumbnailPath = path.join(thumbnailDir, req.file.filename); if (fs.existsSync(thumbnailPath)) { fs.unlinkSync(thumbnailPath); } return res.status(500).json({ error: '保存文件信息失败' }); } // 文件上传成功 res.json({ success: true, filename: req.file.filename, originalName: req.file.originalname }); } catch (error) { // 发生错误时,删除已上传的文件和可能存在的缩略图 if (req.file) { fs.unlinkSync(req.file.path); const thumbnailPath = path.join(thumbnailDir, req.file.filename); if (fs.existsSync(thumbnailPath)) { fs.unlinkSync(thumbnailPath); } } console.error('文件上传错误:', error); res.status(500).json({ error: '文件上传失败' }); } }); // 文件删除 app.delete('/delete/:filename', (req, res) => { try { // 解码文件名 const decodedFilename = decodeURIComponent(req.params.filename); const filePath = path.join(uploadDir, decodedFilename); const thumbnailPath = path.join(thumbnailDir, decodedFilename); // 先检查文件信息是否存在 const fileInfo = getFileByNewName(decodedFilename); if (!fileInfo) { return res.status(404).json({ error: '文件不存在' }); } // 删除原文件和缩略图 fs.unlink(filePath, (err) => { if (err && err.code !== 'ENOENT') { return res.status(500).json({ error: '删除文件失败' }); } // 尝试删除缩略图(如果存在) if (fs.existsSync(thumbnailPath)) { try { fs.unlinkSync(thumbnailPath); } catch (error) { console.error('删除缩略图错误:', error); // 继续处理,即使缩略图删除失败 } } // 从JSON中删除文件信息 const success = removeFileInfo(decodedFilename); if (!success) { return res.status(500).json({ error: '删除文件信息失败' }); } res.json({ success: true, message: `文件 ${fileInfo.originalName} 已删除` }); }); } catch (error) { console.error('文件删除错误:', error); res.status(400).json({ error: '无效的文件名' }); } }); // 启动服务器 app.listen(port, () => { console.log(`文件传输系统运行在 http://你的IP地址:${port}`); });

欢迎大家来到IT世界,在知识的湖畔探索吧!

自制局域网文件传输工具



欢迎大家来到IT世界,在知识的湖畔探索吧!

在node和bun下都可以运行

源码地址:
https://github.com/ganshenmail/file-transfer

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/137990.html

(0)
上一篇 32分钟前
下一篇 7分钟前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信