
本文详解如何在 react 应用中不依赖 cloudinary 前端 widget 或 jquery sdk,直接通过原生 javascript 调用 cloudinary rest api 实现大文件(>100mb)的无后端中转、分片上传,支持 unsigned 与 signed 两种安全模式。
Cloudinary 官方 node.js 后端 SDK(如 cloudinary.v2.uploader.upload_stream 和 upload_large_stream)虽内置完善的流式分片逻辑,但因其强依赖 Node.js 运行时(如 fs, stream, Buffer),无法在浏览器环境中运行。因此,前端需绕过 SDK,采用标准 http 请求 + 分片协议,直接对接 Cloudinary 的 Upload API。
✅ 核心方案:纯前端分片上传(Chunked Upload)
Cloudinary 原生支持客户端分片上传(Chunked Upload),无需任何 SDK。其流程如下:
- 初始化上传会话:向 https://api.cloudinary.com/v1_1/
/auto/upload 发起 POST,携带 upload_preset(unsigned)或签名参数(signed),获取 upload_id; - 分片上传:将文件按 chunk_size(推荐 5–20 MB)切片,逐个调用 PUT /v1_1/
/auto/upload/ ,附带 Content-Range 头; - 完成上传:发送最终 POST 请求确认上传完成,返回资源 URL。
以下为 react 中的精简实现示例(使用 fetch + AbortController):
// utils/cloudinaryUpload.ts export const uploadLargeFile = async ( file: File, cloudName: string, uploadPreset: string, signatureEndpoint?: string // 可选:后端签名接口 ): Promise<{ secure_url: string; public_id: string }> => { const chunkSize = 10 * 1024 * 1024; // 10MB/chunk const totalChunks = Math.ceil(file.size / chunkSize); let uploadId: string; // Step 1: 初始化上传(unsigned 或 signed) const initPayload = { upload_preset: uploadPreset }; if (signatureEndpoint) { const { timestamp, signature } = await fetch(signatureEndpoint, { method: 'POST', body: JSON.stringify({ file_size: file.size, file_type: file.type }), }).then(r => r.json()); Object.assign(initPayload, { timestamp, signature, api_key: 'YOUR_API_KEY' }); } const initRes = await fetch( `https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`, { method: 'POST', body: JSON.stringify(initPayload) } ); const initJson = await initRes.json(); uploadId = initJson.upload_id; // Step 2: 分片上传 for (let i = 0; i < totalChunks; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); const blob = new Blob([chunk], { type: file.type }); await fetch( `https://api.cloudinary.com/v1_1/${cloudName}/auto/upload/${uploadId}`, { method: 'PUT', headers: { 'Content-Range': `bytes ${start}-${end - 1}/${file.size}`, 'Content-Type': file.type, }, body: blob, } ); } // Step 3: 完成上传 const completeRes = await fetch( `https://api.cloudinary.com/v1_1/${cloudName}/auto/upload/${uploadId}`, { method: 'POST' } ); return completeRes.json(); }; // 在组件中调用 function UploadButton() { const handleUpload = async () => { const file = document.querySelector('input[type="file"]')?.files?.[0]; if (!file) return; try { const result = await uploadLargeFile( file, 'your_cloud_name', 'your_upload_preset', '/api/cloudinary/sign' // 若启用 signed 上传 ); console.log('Upload success:', result.secure_url); } catch (err) { console.error('Upload failed:', err); } }; return ( <> > ); }
⚠️ 关键注意事项
- Unsigned 上传限制:仅允许预设白名单中的参数(如 folder, tags, context),且禁止设置 public_id 或 overwrite;适合公开、低敏感场景。
- Signed 上传更安全:需后端生成签名(基于 api_secret + 参数排序 + SHA1),可完全控制上传行为(如强制 public_id、resource_type、eager 转码等)。签名逻辑示例(node.js):
// POST /api/cloudinary/sign app.post('/sign', (req, res) => { const { file_size, file_type, ...rest } = req.body; const timestamp = Math.floor(Date.now() / 1000); const params = { timestamp, file_size, file_type, ...rest }; const sorted = Object.keys(params) .sort() .map(k => `${k}=${params[k]}`) .join('&'); const signature = crypto .createHash('sha1') .update(sorted + process.env.CLOUDINARY_API_SECRET) .digest('hex'); res.json({ timestamp, signature, api_key: process.env.CLOUDINARY_API_KEY }); }); - 错误重试与断点续传:生产环境应添加 fetch 重试机制、AbortController 中断支持,并持久化 upload_id 与已上传分片索引,实现断点续传。
- CORS 配置:确保 Cloudinary 域名(api.cloudinary.com)已在 Cloudinary 控制台的 Settings → Security → CORS 中正确配置允许的前端域名。
✅ 总结
放弃“复用后端 SDK”的思路,转而采用 Cloudinary 原生 Chunked Upload API,是 React 等现代前端框架实现大文件直传的最佳实践。它轻量、可控、符合安全规范,且完全规避了服务端带宽与内存瓶颈。结合 signed 上传与后端签名服务,即可在保障灵活性的同时满足企业级安全审计要求。
立即学习“前端免费学习笔记(深入)”;