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

基于Server Actions的多文件上传真实进度条实现咨询

用Server Actions实现真实上传进度条的方案

核心问题

Server Actions默认的调用方式(如formAction、直接函数调用)被Next.js封装,无法直接获取上传进度事件。要实现真实进度,需手动构造请求并通过XMLHttpRequest监听上传进度(Fetch API暂不支持可靠的上传进度监听)。


步骤1:编写支持流式处理的Server Action

用busboy处理文件上传,避免一次性读入内存,确保进度跟踪真实有效:

// app/actions/upload.ts
'use server';

import busboy from 'busboy';
import { createWriteStream } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Readable } from 'stream';

export async function uploadFiles(request: Request) {
  return new Promise((resolve, reject) => {
    const bb = busboy({ headers: request.headers });
    
    // 处理每个上传的文件
    bb.on('file', (_name, fileStream, { filename }) => {
      const savePath = join(tmpdir(), filename);
      fileStream.pipe(createWriteStream(savePath));
      
      fileStream.on('end', () => {
        console.log(`文件 ${filename} 已写入临时目录`);
      });
    });
    
    // 所有文件处理完成
    bb.on('finish', () => {
      resolve({ success: true, message: '全部文件上传完成' });
    });
    
    // 处理错误
    bb.on('error', (err) => {
      reject(new Error(`上传失败: ${err.message}`));
    });
    
    // 将请求体流转给busboy
    Readable.fromWeb(request.body as ReadableStream).pipe(bb);
  });
}

步骤2:前端用XMLHttpRequest实现进度监听

放弃默认的Server Action调用方式,手动构造请求,通过xhr.upload.onprogress跟踪真实进度:

// app/upload/page.tsx
'use client';

import { useState } from 'react';

export default function UploadPage() {
  const [uploadProgress, setUploadProgress] = useState(0);

  const handleUpload = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const form = e.currentTarget;
    const fileInput = form.querySelector('input[type="file"]');
    if (!fileInput?.files || fileInput.files.length === 0) return;

    // 重置进度
    setUploadProgress(0);

    // 构造请求数据
    const formData = new FormData();
    formData.append('__NEXT_ACTION__', JSON.stringify({
      action: 'app/actions/upload/uploadFiles', // Server Action的完整路径
      args: [],
      caller: 'client',
    }));
    // 添加所有选中的文件
    Array.from(fileInput.files).forEach(file => {
      formData.append('files', file);
    });

    // 初始化XMLHttpRequest
    const xhr = new XMLHttpRequest();
    xhr.open('POST', '/api/_next/server-action');

    // 监听上传进度
    xhr.upload.addEventListener('progress', (event) => {
      if (event.lengthComputable) {
        const percent = Math.round((event.loaded / event.total) * 100);
        setUploadProgress(percent);
      }
    });

    // 监听请求完成
    xhr.addEventListener('load', () => {
      if (xhr.status === 200) {
        const result = JSON.parse(xhr.responseText);
        alert(result.message);
      } else {
        alert('上传失败,请重试');
      }
      setUploadProgress(0);
    });

    // 监听请求错误
    xhr.addEventListener('error', () => {
      alert('上传出错,请检查网络');
      setUploadProgress(0);
    });

    // 发送请求
    xhr.send(formData);
  };

  return (
    <div className="upload-container">
      <form onSubmit={handleUpload}>
        <input type="file" multiple name="files" />
        <button type="submit">开始上传</button>
      </form>

      {/* 进度条 */}
      <div className="progress-wrapper">
        <div 
          className="progress-bar" 
          style={{ width: `${uploadProgress}%` }}
        ></div>
      </div>
      <p>{uploadProgress}%</p>
    </div>
  );
}

关键注意事项

  1. Server Action路径:__NEXT_ACTION__中的action字段要和你的Server Action文件路径完全匹配(如app/actions/upload.ts中的uploadFiles对应app/actions/upload/uploadFiles)。
  2. 流式处理:必须用流式方式处理文件(如busboy),如果直接用await file.arrayBuffer()会一次性读取整个文件,进度条会直接跳到100%。
  3. XMLHttpRequest的必要性:Fetch API目前没有原生支持上传进度的可靠方案,XMLHttpRequest是当前最兼容的选择。
  4. 依赖安装:如果用busboy,需要安装依赖:npm install busboy。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:06:02