NodeJS+Express+Mongo+IPFS多步Post路由高效实现与问题排查
问题排查与最优实现方案
核心问题定位
- 第一,你用全局EventEmitter绑定事件的实现逻辑完全错误:每次请求进入路由都会给emitter新增一组
on监听器,多次请求后监听器会重复触发,不同请求的变量还会互相污染,完全不能用来控制单请求的异步流程。而且你用的是on而非once,监听器会一直驻留内存,既会造成内存泄漏,也会导致后续请求的流程完全混乱。 - 第二,异步操作没有等待前序IO完全完成就执行下一步:你遇到的IPFS上传文件只有6字节、哈希固定的问题,90%以上概率是加密函数还没把完整的加密文件写入磁盘,你就已经开始读取文件上传IPFS,读到的是空文件或者不完整的临时文件,所以哈希永远相同。
- 第三,异步错误完全没有捕获:任意一步执行出错都会导致请求卡住超时,客户端收不到响应。
最优多步异步路由实现方案
你已经在用async/await语法,完全不需要用EventEmitter控制流程,直接把回调式API转成Promise后按顺序串行执行即可,逻辑清晰且没有副作用。
优化后代码
const fs = require('fs/promises') const path = require('path') // 先把回调形式的file.mv转成Promise const moveFile = (file, targetPath) => { return new Promise((resolve, reject) => { file.mv(targetPath, err => err ? reject(err) : resolve()) }) } router.post('/', async(req, res) => { // 提前定义需要清理的文件路径,出错时统一清理 let uploadFilePath = null let encryptFilePath = null try { // 1. 参数校验 if (!req.session.loggedIn || !req.files) { return res.status(400).json({ message: 'Bad Request received check your request again, bruh.' }) } // 2. 处理文件名,移动文件到uploads目录 const rawFile = req.files.file const ext = path.extname(rawFile.name) const fileName = req.body.filename + ext uploadFilePath = path.join('./uploads', fileName) await moveFile(rawFile, uploadFilePath) // 3. 加密文件,等待加密完全完成 const authKey = req.session.authKey encryptFilePath = path.join('./tmp', `${fileName}.enc`) // 注意:确保你的encrypt函数是真·异步函数,只有文件完全写入磁盘后才resolve await encrypt({ file: uploadFilePath, password: authKey }) // 4. 上传加密文件到IPFS,先校验文件是否存在且大小正常 const encFileStat = await fs.stat(encryptFilePath) console.log(`待上传加密文件大小:${encFileStat.size}字节`) // 修正你原来的传参错误,只传正确的加密文件路径 const fileHash = await addFile(encryptFilePath) // 5. 写入MongoDB await db.IPFS.create({ owner: db.User._id, title: fileName, content: req.body.content, dbCID: fileHash, tags: req.body.tags }) // 6. 清理临时文件 await Promise.allSettled([ fs.unlink(uploadFilePath), fs.unlink(encryptFilePath) ]) // 7. 返回响应 res.redirect('/recap') } catch (err) { console.error('路由执行出错:', err) // 出错时也要清理已经生成的临时文件,避免磁盘占用 if (uploadFilePath) await fs.unlink(uploadFilePath).catch(() => {}) if (encryptFilePath) await fs.unlink(encryptFilePath).catch(() => {}) res.status(500).json({ message: '服务端处理失败', error: err.message }) } })
剩余问题排查方向
如果修改后还是出现IPFS文件大小异常的问题,按以下顺序排查:
- 先注释掉IPFS上传和后续清理逻辑,看
./tmp目录下生成的.enc文件大小是否正常,如果大小异常,修改你的encrypt函数,确保加密文件完全写入磁盘后再resolve Promise,不要在文件流还没关闭的时候就返回。 - 如果加密文件大小正常,检查你的
addFile函数,确认你是读取传入的路径对应的文件内容上传,而非读取了固定路径的空文件/错误文件。
内容的提问来源于stack exchange,提问作者Peter Roto
相关产品推荐
相关产品推荐

