大文件分片上传 — nodejs

上传文件路由:

var express = require('express');
var router = express.Router();
const multer = require('multer');
const fs = require('fs');
const path = require('path');// 确保上传目录存在
const uploadDir = path.join(__dirname, '../backend/uploads');
const tempDir = path.join(uploadDir, 'temp');
if (!fs.existsSync(uploadDir)) {fs.mkdirSync(uploadDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) {fs.mkdirSync(tempDir, { recursive: true });
}// 创建文件哈希存储
const hashFilePath = path.join(__dirname, '../backend/fileHashes.json');
let fileHashes = {};try {if (fs.existsSync(hashFilePath)) {const data = fs.readFileSync(hashFilePath, 'utf8');fileHashes = JSON.parse(data || '{}');}
} catch (err) {console.error('Error reading hash file:', err);
}// 保存文件哈希
function saveFileHashes() {fs.writeFileSync(hashFilePath, JSON.stringify(fileHashes, null, 2), 'utf8');
}// 配置Multer进行切片上传 - 修复:使用查询参数
const chunkStorage = multer.diskStorage({destination: (req, file, cb) => {// 从查询参数中获取fileHashconst fileHash = req.query.fileHash;if (!fileHash) return cb(new Error('Missing file hash'));const chunkDir = path.join(tempDir, fileHash);if (!fs.existsSync(chunkDir)) {fs.mkdirSync(chunkDir, { recursive: true });}cb(null, chunkDir);},filename: (req, file, cb) => {// 从查询参数中获取chunkIndexconst chunkIndex = req.query.chunkIndex;cb(null, `chunk-${chunkIndex}`);},
});const uploadChunk = multer({ storage: chunkStorage });// 文件检查端点
router.post('/check', express.json(), (req, res) => {const { fileName, fileSize, fileHash, algorithm = 'MD5' } = req.body;if (!fileHash) {return res.status(400).json({exists: false,message: '缺少文件哈希值',});}// 检查文件哈希是否已存在if (fileHashes[fileHash]) {return res.json({exists: true,message: `文件已存在: ${fileHashes[fileHash].fileName}`,});}res.json({exists: false,message: '文件不存在,可以上传',});
});// 检查切片端点
router.post('/checkChunks', express.json(), (req, res) => {const { fileHash, totalChunks } = req.body;if (!fileHash) {return res.status(400).json({message: '缺少文件哈希值',});}const chunkDir = path.join(tempDir, fileHash);const uploadedChunks = [];if (fs.existsSync(chunkDir)) {const files = fs.readdirSync(chunkDir);files.forEach((file) => {if (file.startsWith('chunk-')) {const chunkIndex = parseInt(file.split('-')[1]);if (!isNaN(chunkIndex)) {uploadedChunks.push(chunkIndex);}}});}res.json({uploadedChunks,message: `已找到 ${uploadedChunks.length}/${totalChunks} 个切片`,});
});// 切片上传端点 - 增加完整性检查
router.post('/uploadChunk', uploadChunk.single('file'), (req, res) => {if (!req.file) {return res.status(400).json({success: false,message: '没有切片被上传',});}// 从查询参数中获取值const fileHash = req.query.fileHash;const chunkIndex = req.query.chunkIndex;const expectedSize = parseInt(req.query.chunkSize);if (!fileHash || !chunkIndex || isNaN(expectedSize)) {// 清理无效上传try {if (req.file.path) {fs.unlinkSync(req.file.path);}} catch (err) {console.error('删除无效切片失败:', err);}return res.status(400).json({success: false,message: '缺少必要参数',});}try {// 验证切片大小const stats = fs.statSync(req.file.path);if (stats.size !== expectedSize) {fs.unlinkSync(req.file.path);return res.status(400).json({success: false,message: `切片大小不匹配: 预期 ${expectedSize} 字节, 实际 ${stats.size} 字节`,expectedSize,actualSize: stats.size,});}res.json({success: true,message: '切片上传成功',chunkIndex: parseInt(chunkIndex),fileHash,});} catch (err) {console.error(`切片验证失败: ${req.file.path}`, err);try {if (fs.existsSync(req.file.path)) {fs.unlinkSync(req.file.path);}} catch (cleanupErr) {console.error('删除切片失败:', cleanupErr);}res.status(500).json({success: false,message: '切片验证失败',error: err.message,});}
});// 合并切片端点
router.post('/merge', express.json(), (req, res) => {const { fileHash, fileName, totalChunks } = req.body;if (!fileHash || !fileName || totalChunks === undefined) {return res.status(400).json({success: false,message: '缺少必要参数',});}const chunkDir = path.join(tempDir, fileHash);const mergedFilePath = path.join(uploadDir, `${fileHash}-${fileName}`);// 验证所有切片是否都存在let allChunksExist = true;for (let i = 0; i < totalChunks; i++) {const chunkPath = path.join(chunkDir, `chunk-${i}`);if (!fs.existsSync(chunkPath)) {allChunksExist = false;break;}}if (!allChunksExist) {return res.status(400).json({success: false,message: '部分切片缺失,无法合并',});}// 合并文件try {const writeStream = fs.createWriteStream(mergedFilePath);const mergeChunks = (index) => {if (index >= totalChunks) {writeStream.end(() => {// 合并完成后删除临时目录fs.rm(chunkDir, { recursive: true }, (err) => {if (err) console.error('删除临时目录失败:', err);// 记录文件信息const stats = fs.statSync(mergedFilePath);fileHashes[fileHash] = {fileName: fileName,filePath: mergedFilePath,fileSize: stats.size,uploadDate: new Date().toISOString(),hash: fileHash,algorithm: 'MD5',};saveFileHashes();res.json({success: true,message: '文件合并成功',filePath: mergedFilePath,});});});return;}const chunkPath = path.join(chunkDir, `chunk-${index}`);const readStream = fs.createReadStream(chunkPath);readStream.pipe(writeStream, { end: false });readStream.on('end', () => {// 删除已合并的切片fs.unlink(chunkPath, (err) => {if (err) console.error(`删除切片 ${index} 失败:`, err);mergeChunks(index + 1);});});readStream.on('error', (err) => {writeStream.close();console.error(`读取切片 ${index} 失败:`, err);res.status(500).json({success: false,message: '合并文件失败',error: err.message,});});};mergeChunks(0);} catch (err) {console.error('合并文件失败:', err);res.status(500).json({success: false,message: '合并文件失败',error: err.message,});}
});module.exports = router;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87072.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS File和base64字符串转换

1. HarmonyOS File和base64字符串转换 1.1. Base64 1.1.1. Base64认知 Base64 是一种基于64个 ASCII 字符来表示二进制数据的表示方法&#xff0c;这个64个不同的字符为&#xff1a;   &#xff08;1&#xff09;大、小写字母&#xff08;A– Z、a–z&#xff09;。52个  …

【NodeJs】【npm】npm安装electron报错

解决问题 npm安装electron报错一般来说是镜像源的问题。 electron的镜像源与一般的 vue 之类的镜像源地址不一样需要单独配置。 npm读取的全局配置一般是在 C:\Users\{用户}\.npmrc 这个配置文件中。 如果你找不到你的配置文件可以执行如下命令, # 执行后会直接用txt打开你的…

植物small RNA靶基因预测软件,psRobot

psRoto软件安装 网址 http://omicslab.genetics.ac.cn/psRobot/downloads.php下载和安装 wget http://omicslab.genetics.ac.cn/psRobot/program/WebServer/psRobot_v1.2.tar.gz # tar -zxvf psRobot_v1.2.tar.gz # cd psRobot_v1.2 ## ./configure make make installpsRot…

翻译服务器

基于UDP编程博客里的回显服务器代码,翻译服务只需要改process方法即可 所以我们可以创建一个UdpDictServer直接继承UdpEchoServer然后重写process方法 在重写的方法中完成翻译的过程 代码: package network;import java.io.IOException; import java.net.SocketException; …

初等变换 线性代数

初等变换 介绍了三种初等变换的操作。 初等矩阵 初等矩阵是干嘛的呢&#xff1f;实际上初等矩阵就是我们矩阵的初等操作&#xff0c;每一个对矩阵的初等变换操作都相当于乘上一个初等矩阵。 左乘初等矩阵就相当于对行进行初等操作&#xff0c;右乘则相当于对列进行初等操作。…

Java基础 集合框架 队列架构 双端队列 Deque

双端队列 Deque Deque 方法简介Deque 核心特点Deque实现类 ArrayDequeArrayDeque 构造方法ArrayDeque 的数据结构及实现原理ArrayDeque 方法介绍ArrayDeque 核心特性ArrayDeque 总结ArrayDeque 使用样例代码 Deque实现类 LinkedListDeque实现类 ConcurrentLinkedDeque (非阻塞线…

【Spring】——事务、整合、注解

目录 一.Spring与mybatis的整合 1.配置文件 ​编辑2. 二.事务 1.事务属性 2.传播属性 3.异常属性 4.常见配置 三.注解 1.什么是注解 2.Autowired 1.用户自定义注解 ​编辑​编辑2.JDK类型注入value 3.Bean 1.对象的创建 2.对象创建次数 3.Bean注解的注入 1.自…

Linux 离线下安装gcc、g++

描述 离线时编译Redis、nginx等编译包&#xff0c;需要gcc安装包&#xff0c;评论提醒我 上传补充 操作 1、进入gcc目录&#xff0c;并执行安装命令 rpm -ivh *.rpm --nodeps --force查看版本 gcc -v2、进入gcc-c目录&#xff0c;并执行安装 rpm -ivh *.rpm --nodeps --f…

融智学定律3:流动创造价值仅当跨域协同

关键公式意义&#xff1a; 人流方程中的 α/β 反映城市吸引力不对称性 物流优化中的 η 实现时间价值货币化 金流模型的 σ(⋅) 捕捉市场情绪突变点 信息熵的 ∥gi​−gj​∥ 度量知识势差驱动 当五流在黎曼流形上满足 ∇_μ​T^μν0&#xff08;能量动量守恒&#xff09…

趣味数据结构之——数组

你们一定都听说过它的故事…… 是的没错&#xff0c;就是一个萝卜一个坑。ಥ◡ಥ 想象一下数组就是那个坑&#xff0c;那么定义数组就是在挖坑。 元素就是萝卜。 坑就在那里(地上)&#xff0c;整整齐齐地排在那里。 于是数组最重要的一个特性就显现出来了——随机存取。还…

PR-2025《Scaled Robust Linear Embedding with Adaptive Neighbors Preserving》

核心思想分析 这篇论文的核心思想在于解决线性嵌入&#xff08;linear embedding&#xff09;与非线性流形结构之间的不匹配问题。传统方法通过保留样本点间的亲和关系来提取数据的本质结构&#xff0c;但这种方法在某些情况下无法有效捕捉到数据的全局或局部特性。此外&#…

Redis-渐进式遍历

之前使用的keys查找key,一次获取到了所有的key,当key较多时,这个操作就有可能造成Redis服务器阻塞.特别是keys *操作. 于是可以通过渐进式遍历,每次获取部分key,通过多次遍历,既查询到了所有的key,又不会卡死服务器. 渐进式遍历不是通过一个命令获取到所有元素的,而是由一组命…

ISP Pipeline(3):Lens Shading Correction 镜头阴影校正

上一篇文章讲的是&#xff1a;ISP Pipeline&#xff08;2&#xff09;&#xff1a; Black Level Compensation:ISP Pipeline&#xff08;2&#xff09;&#xff1a;Black Level Compensation 黑电平补偿-CSDN博客 视频&#xff1a;(4) Lens Shading Correction | Image Signal…

什么是WebAssembly(WASM)

WebAssembly&#xff08;WASM&#xff09; 是一种高性能的低级编程语言字节码格式&#xff0c;可在网页和非网页环境中运行&#xff0c;支持多语言编译&#xff0c;运行速度接近原生代码。它在区块链中的作用是&#xff1a;作为智能合约的执行引擎&#xff0c;被多条非以太坊链…

【C++】inline的作用

一、inline的作用 1.1函数内联 作用​&#xff1a;建议编译器将函数调用替换为函数体代码&#xff0c;减少函数调用的开销&#xff08;压栈/跳转&#xff09;。​注意​&#xff1a;这只是对编译器的建议&#xff0c;编译器可能忽略&#xff08;如函数体过大或递归&#xff0…

代码随想录|图论|04广度优先搜索理论基础

广搜的使用场景 广搜的搜索方式就适合于解决两个点之间的最短路径问题。 因为广搜是从起点出发&#xff0c;以起始点为中心一圈一圈进行搜索&#xff0c;一旦遇到终点&#xff0c;记录之前走过的节点就是一条最短路。 当然&#xff0c;也有一些问题是广搜 和 深搜都可以解决…

Xposed框架深度解析:Android系统级Hook实战指南

引言:Android系统定制化的革命性突破 在移动安全研究和系统优化领域,传统的APP修改方案面临​​三重技术瓶颈​​: ​​逆向工程壁垒​​:APK重打包方案需处理签名校验、代码混淆等防护,平均耗时增加200%​​兼容性挑战​​:Android碎片化导致设备适配率不足65%​​功能…

大模型在通讯网络中的系统性应用架构

一、网络架构智能化重构​​ ​​1.1 空天地一体化组网优化​​ 智能拓扑动态调整​​&#xff1a;大模型通过分析卫星轨道数据、地面基站负载及用户分布&#xff0c;实时优化天地一体化网络拓扑。例如&#xff0c;在用户密集区域&#xff08;如城市中心&#xff09;自动增强低…

软件测试进阶:Python 高级特性与数据库优化(第二阶段 Day6)

在掌握 SQL 复杂查询和 Python 数据库基础操作后&#xff0c;第六天将深入探索Python 高级编程特性与数据库性能优化。通过掌握 Python 的模块与包管理、装饰器等高级语法&#xff0c;结合数据库索引优化、慢查询分析等技术&#xff0c;提升测试工具开发与数据处理效率。 一、…

【NLP】自然语言项目设计04

目录 04模型验证 代码架构核心设计说明 05运行推理 代码架构核心设计说明 项目展望 项目简介 训练一个模型&#xff0c;实现歌词仿写生成 任务类型&#xff1a;文本生成&#xff1b; 数据集是一份歌词语料&#xff0c;训练一个模型仿写歌词。 要求 1.清洗数据。歌词语料…