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

React调用LWC上传大文件至Salesforce的解决方案求助

问题根源

你当前用FileReader.readAsDataURL()将文件转成base64字符串传递给Salesforce的方式,存在两个核心限制:

  1. base64编码会让文件体积膨胀约33%,20MB文件转base64后会变成~26.7MB;
  2. Salesforce Apex方法的字符串参数有大小上限(单字符串约6MB),远超这个范围的参数会直接上传失败。

下面是两种可行的20MB文件上传解决方案:


方案一:分片上传(推荐)

把大文件拆成多个1-2MB的小片段,逐个上传到Salesforce,最后合并成完整的Attachment。

1. React端修改上传逻辑

替换原onChange中的文件处理代码,实现分片和上传:

const CHUNK_SIZE = 1.5 * 1024 * 1024; // 1.5MB分片,避免base64超Apex字符串限制

async function uploadChunks(file: File, fileName: string, uid: string) {
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  let chunkIndex = 0;

  while (chunkIndex < totalChunks) {
    const start = chunkIndex * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);

    // 转base64
    const reader = new FileReader();
    await new Promise<void>(resolve => {
      reader.onloadend = () => resolve();
      reader.readAsDataURL(chunk);
    });
    const chunkBase64 = reader.result as string;

    // 通知LWC上传当前分片
    await window.parent.postMessage({
      type: 'UPLOAD_CHUNK',
      payload: { chunkBase64, chunkIndex, totalChunks, fileName, uid }
    }, window.location.origin);

    chunkIndex++;
  }

  // 所有分片上传完成,通知合并
  window.parent.postMessage({
    type: 'MERGE_CHUNKS',
    payload: { fileName, uid }
  }, window.location.origin);
}

// 修改原onChange逻辑
if (status === DRAG_STATUSES.done) {
  uploadChunks(info.file.originFileObj, info.file.name, info.file.uid)
    .then(() => message.success(`${info?.file.name} 文件上传成功`))
    .catch(() => message.error(`${info?.file.name} 文件上传失败`));
}

2. LWC端接收消息并调用Apex

import { LightningElement } from 'lwc';
import uploadChunk from '@salesforce/apex/FileUploadController.uploadChunk';
import mergeChunks from '@salesforce/apex/FileUploadController.mergeChunks';

export default class ReactUploadWrapper extends LightningElement {
  connectedCallback() {
    window.addEventListener('message', this.handleMessage.bind(this));
  }

  disconnectedCallback() {
    window.removeEventListener('message', this.handleMessage.bind(this));
  }

  async handleMessage(event) {
    if (event.origin !== window.location.origin) return;

    if (event.data.type === 'UPLOAD_CHUNK') {
      const { chunkBase64, chunkIndex, totalChunks, fileName, uid } = event.data.payload;
      try {
        await uploadChunk({ chunkBase64, chunkIndex, totalChunks, fileName, fileUid: uid });
      } catch (err) {
        event.source.postMessage({ type: 'CHUNK_ERROR', payload: { uid } }, event.origin);
      }
    } else if (event.data.type === 'MERGE_CHUNKS') {
      const { fileName, uid } = event.data.payload;
      try {
        await mergeChunks({ fileName, fileUid: uid });
      } catch (err) {
        event.source.postMessage({ type: 'MERGE_ERROR', payload: { uid } }, event.origin);
      }
    }
  }
}

3. Apex控制器实现分片存储与合并

用临时Attachment存储分片,避免自定义对象的字段限制:

public with sharing class FileUploadController {
  @AuraEnabled
  public static void uploadChunk(String chunkBase64, Integer chunkIndex, Integer totalChunks, String fileName, String fileUid) {
    // 移除base64前缀
    String cleanBase64 = chunkBase64.split(',')[1];
    Blob chunkBlob = EncodingUtil.base64Decode(cleanBase64);

    // 存储分片到临时Attachment
    Attachment chunkAtt = new Attachment(
      Name = fileUid + '_chunk_' + chunkIndex,
      Body = chunkBlob,
      ParentId = '001XXXXXXXXXXXX', // 替换为临时父记录ID,或专门的分片存储记录
      Description = JSON.serialize(new Map<String, Object>{
        'fileUid' => fileUid,
        'chunkIndex' => chunkIndex,
        'totalChunks' => totalChunks
      })
    );
    insert chunkAtt;
  }

  @AuraEnabled
  public static Id mergeChunks(String fileName, String fileUid) {
    // 查询所有分片并按顺序排序
    List<Attachment> chunkAtts = [
      SELECT Body 
      FROM Attachment 
      WHERE Name LIKE :fileUid + '_chunk_%' 
      ORDER BY Name ASC
    ];

    // 合并分片Blob
    Blob fullBlob = new Blob();
    for (Attachment att : chunkAtts) {
      fullBlob = fullBlob + att.Body;
    }

    // 创建最终Attachment
    Attachment finalAtt = new Attachment(
      Name = fileName,
      Body = fullBlob,
      ParentId = '001XXXXXXXXXXXX' // 替换为实际业务父记录ID
    );
    insert finalAtt;

    // 删除临时分片
    delete chunkAtts;
    return finalAtt.Id;
  }
}

方案二:直接调用Salesforce REST API上传Blob

绕过Apex字符串限制,直接用multipart/form-data格式上传原始Blob文件。

1. LWC暴露会话信息

import { LightningElement, wire } from 'lwc';
import { getSessionId } from 'lightning/userContextApi';
import { getBaseUrl } from '@salesforce/community/baseUrl';

export default class ReactUploadWrapper extends LightningElement {
  @wire(getSessionId)
  handleSessionId({ data }) {
    if (data) window.sessionStorage.setItem('sfSessionId', data);
  }

  @wire(getBaseUrl)
  handleBaseUrl({ data }) {
    if (data) window.sessionStorage.setItem('sfInstanceUrl', data);
  }
}

2. React端直接调用REST API

if (status === DRAG_STATUSES.done) {
  const sessionId = window.sessionStorage.getItem('sfSessionId');
  const instanceUrl = window.sessionStorage.getItem('sfInstanceUrl');
  const parentRecordId = '001XXXXXXXXXXXX'; // 替换为实际父记录ID

  const formData = new FormData();
  formData.append('entity_content', info.file.originFileObj);
  formData.append('entity_attachment', JSON.stringify({
    Name: info.file.name,
    ParentId: parentRecordId
  }));

  fetch(`${instanceUrl}/services/data/v59.0/composite/tree/Attachment`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${sessionId}` },
    body: formData
  })
  .then(res => res.ok ? message.success(`${info?.file.name} 文件上传成功`) : Promise.reject())
  .catch(() => message.error(`${info?.file.name} 文件上传失败`));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 01:40:38