React调用LWC上传大文件至Salesforce的解决方案求助
问题根源
你当前用FileReader.readAsDataURL()将文件转成base64字符串传递给Salesforce的方式,存在两个核心限制:
- base64编码会让文件体积膨胀约33%,20MB文件转base64后会变成~26.7MB;
- 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
相关产品推荐
相关产品推荐

