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

求助:修复基于googleapis的Google Drive分片上传文件不完整问题

Google Drive 分片上传问题排查与修复方案

原代码核心问题

  1. 可恢复上传初始化错误:
    • uploadType参数应放在请求根参数中,而非requestBody内
    • 初始化可恢复上传时,Google API返回的是可恢复上传URL,而非直接生成fileId,原代码错误地提前获取res.data.id,此时文件并未真正创建
  2. 分片上传方式错误:
    不应调用drive.files.update方法,需使用初始化得到的可恢复URL直接发送PUT请求传输分片

修复后的完整代码

const fs = require("fs");
const { google } = require("googleapis");
const axios = require("axios"); // 需安装axios:npm install axios
const { getGoogleClient } = require("../../../utils/google");

async function uploadFile(file) {
  const auth = getGoogleClient();
  const drive = google.drive({
    auth,
    version: "v3",
  });

  const folderId = process.env.GOOGLE_FOLDER_ID;
  const chunkSize = 1024 * 1024; // 1MB分片
  const filePath = file.path;
  const fileName = file.name;
  const fileSize = fs.statSync(filePath).size;

  if (fileSize <= chunkSize) {
    // 小文件直接上传
    const media = {
      mimeType: file.type,
      body: fs.createReadStream(filePath),
    };

    await drive.files.create({
      requestBody: { name: fileName, parents: [folderId] },
      media,
      supportsAllDrives: true,
    });
  } else {
    // 初始化可恢复上传
    const initResponse = await drive.files.create({
      requestBody: { name: fileName, parents: [folderId] },
      uploadType: "resumable", // 正确放置uploadType参数
      supportsAllDrives: true,
      headers: {
        "X-Upload-Content-Type": file.type, // 使用文件实际MIME类型
        "X-Upload-Content-Length": fileSize.toString(),
      },
    });

    // 获取可恢复上传URL
    const resumableUrl = initResponse.headers["location"];
    if (!resumableUrl) {
      throw new Error("Failed to get resumable upload URL");
    }

    let start = 0;
    let chunkIndex = 0;

    while (start < fileSize) {
      const end = Math.min(start + chunkSize, fileSize);
      const chunkLength = end - start;

      // 创建分片读取流(fs的end参数是排他的,需减1)
      const chunkStream = fs.createReadStream(filePath, {
        start,
        end: end - 1,
      });

      // 准备请求头
      const headers = {
        "Content-Type": file.type,
        "Content-Range": `bytes ${start}-${end - 1}/${fileSize}`,
        "Content-Length": chunkLength.toString(),
      };

      // 发送分片上传请求
      const uploadResponse = await axios.put(resumableUrl, chunkStream, {
        headers,
        auth: {
          bearer: await auth.getAccessToken(), // 手动携带access token
        },
      });

      // 处理上传状态
      if (uploadResponse.status === 200) {
        console.log("File uploaded successfully:", uploadResponse.data.id);
        break;
      } else if (uploadResponse.status === 308) {
        // 308表示需继续上传,解析已完成的范围
        const range = uploadResponse.headers["range"];
        start = range ? parseInt(range.split("-")[1]) + 1 : end;
      }

      console.log(`Chunk ${chunkIndex} uploaded, next start at ${start}`);
      chunkIndex++;
    }
  }

  console.log("Done!", fileName);
}

关键修改说明

  • 初始化流程:修正uploadType参数位置,正确获取可恢复上传URL
  • 分片传输:改用axios直接请求resumable URL,适配Google Drive的可恢复上传协议
  • 断点续传支持:处理308状态码,从已上传位置继续传输
  • 参数统一:使用文件实际MIME类型,修正分片读取范围的边界问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:53:25