You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Adonis JS中实现大文件分片上传,避免请求超时问题?

AdonisJS 大文件分片上传解决方案

问题根因说明

  • 你当前的服务端代码仅支持完整单文件上传,没有适配客户端的分片逻辑:客户端将大文件切割为多个独立的blob分片,你直接存储单个分片,重命名后自然无法正常打开
  • 大文件走单请求上传必然会触发服务器、反向代理(如Nginx)的超时限制,也容易因网络波动导致整个上传失败

完整实现步骤

1. 调整基础配置放宽上传限制

首先修改config/bodyparser.ts,避免小分片被框架拦截:

// config/bodyparser.ts
export default {
  multipart: {
    // 单个分片最大大小,按需调整,示例设为100MB
    maxFileSize: '100mb',
    // 按需添加允许的文件后缀
    allowedExtensions: ['png', 'jpg', 'jpeg', 'mp4', 'avi', 'mov'],
  }
}

如果使用Nginx做反向代理,同步修改Nginx配置:

client_max_body_size 100m;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

2. 服务端分片逻辑改造

首先要求客户端上传时额外携带3个参数:

  • file_hash:整个文件的唯一哈希值,用于标识属于同一个文件的所有分片
  • chunk_index:当前分片的序号,从0开始计数
  • total_chunks:当前文件总分片数

路由调整

// routes.ts
apiGroup('v1', 'files', Route.group(async () => {
  // 分片上传接口
  Route.post('upload-chunk', 'Files/UploadController.uploadChunk')
  // 全部分片上传完成后的合并接口
  Route.post('merge-chunks', 'Files/UploadController.mergeChunks')
}))

UploadController改造

// UploadController.ts
'use strict'
import {HttpContextContract} from "@ioc:Adonis/Core/HttpContext";
import Application from "@ioc:Adonis/Core/Application";
import MediaRepositories from "App/Repositories/MediaRepositories";
import fs from 'fs/promises'
import path from 'path'

export default class UploadController {
   // 接收单个分片
   public async uploadChunk({request, response}:HttpContextContract){
     const file = request.file('file')
     const fileHash = request.input('file_hash')
     const chunkIndex = request.input('chunk_index')
     if (!file || !fileHash || chunkIndex === undefined) {
       return response.badRequest({msg: '参数缺失'})
     }
     // 同一文件的分片统一存放在独立临时目录
     const chunkDir = path.join(Application.tmpPath('chunks'), fileHash)
     await fs.mkdir(chunkDir, {recursive: true})
     // 分片按序号命名存储
     await file.move(chunkDir, {name: chunkIndex.toString()})
     return {msg: '分片上传成功'}
   }

   // 合并全部分片生成完整文件
   public async mergeChunks({request, response}:HttpContextContract) {
     const fileHash = request.input('file_hash')
     const totalChunks = request.input('total_chunks')
     const fileName = request.input('file_name') // 客户端传递原文件名
     if (!fileHash || !totalChunks || !fileName) {
       return response.badRequest({msg: '参数缺失'})
     }
     const finalPath = path.join(Application.publicPath('media/transientmodels'), fileName)
     const chunkDir = path.join(Application.tmpPath('chunks'), fileHash)
     // 按顺序读取所有分片写入最终文件
     const writeStream = fs.createWriteStream(finalPath)
     for (let i = 0; i < totalChunks; i++) {
       const chunkPath = path.join(chunkDir, i.toString())
       const chunkBuffer = await fs.readFile(chunkPath)
       writeStream.write(chunkBuffer)
       // 写入完成后删除已处理分片
       await fs.unlink(chunkPath)
     }
     writeStream.end()
     // 合并完成后删除空的临时分片目录
     await fs.rmdir(chunkDir)
     // 可调用MediaRepositories做后续的文件校验、数据入库逻辑
     const mediaInfo = await MediaRepositories.createMedia(finalPath, fileName)
     return {msg: '文件上传完成', data: mediaInfo}
   }
}

MediaRepositories调整

// MediaRepositories.ts
'use strict'
import Application from "@ioc:Adonis/Core/Application";

export default class MediaRepositories {
  static async createMedia(filePath: string, fileName: string) {
    // 此处可扩展文件格式校验、文件信息入库等逻辑
    return {
      fileName,
      accessUrl: `/media/transientmodels/${fileName}`
    }
  }

  static async updateMediaById(){}
  static async updateMediaByIds(){}
}

避免请求超时的核心方案

  1. 严格使用分片上传,将单个请求的文件大小控制在10-50MB之间,单个请求耗时远低于常规服务超时阈值
  2. 可扩展断点续传逻辑:客户端上传前先查询当前文件已上传的分片序号,跳过已上传部分,网络中断后无需重传整个文件
  3. 针对10GB以上的超大文件,可将分片合并逻辑放到Adonis异步队列中执行,避免合并过程耗时过长触发接口超时

内容的提问来源于stack exchange,提问作者Vladyslav Chalyi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 15:45:03