Swift中base64转Data返回nil 如何正确保存PDF到Firebase存储
问题根因
Swift端Data(base64Encoded:)返回nil不存在什么特殊语法问题,本质是拿到的base64字符串不符合API的解码要求,或是两端编解码逻辑没对齐。下面是两种可直接落地的修复方案:
方案1:直接在云函数端完成PDF上传(最稳妥,推荐)
把base64字符串从云函数传到前端再解码上传完全是多此一举,既容易因为传输过程的字符转义导致base64损坏,还浪费客户端流量,云函数和Firebase Storage走内网传输速度快得多,也不会有编解码问题。
修复后的Node.js云函数代码
const { initializeApp } = require('firebase-admin/app'); const { getStorage } = require('firebase-admin/storage'); const { getFirestore } = require('firebase-admin/firestore'); initializeApp(); const bucket = getStorage().bucket(); const db = getFirestore(); exports.getAssetReportPdf = functions.https.onCall(async (data, context) => { // 必须加鉴权,否则接口裸奔任何人都能拉取用户资产报告 if (!context.auth) { throw new functions.https.HttpsError('unauthenticated', '登录后才可操作'); } const { assetReportToken, phone } = data; const configuration = new Configuration({ basePath: PlaidEnvironments[functions.config().app.env], baseOptions: { headers: { "PLAID-CLIENT-ID": functions.config().plaid.client_id, "PLAID-SECRET": functions.config().plaid.secret, }, // 强制返回二进制数组,避免SDK自动转utf8字符串损坏PDF内容 responseType: 'arraybuffer' }, }); const plaidClient = new PlaidApi(configuration); try { const response = await plaidClient.assetReportPdfGet({ asset_report_token: assetReportToken }); const pdfBuffer = Buffer.from(response.data); // 上传到Storage,路径和你之前前端逻辑保持一致 const file = bucket.file(`${phone}/${phone}`); await file.save(pdfBuffer, { contentType: 'application/pdf' }); // 生成下载URL const [downloadUrl] = await file.getSignedUrl({ action: 'read', expires: '03-01-2500' }); // 直接把URL写入Firestore,省掉前端写库逻辑 await db.collection('users').doc(phone).set({ asset_report: downloadUrl }, { merge: true }); return { success: true, url: downloadUrl }; } catch (err) { console.error('拉取PDF失败', err); throw new functions.https.HttpsError("internal", "PDF下载失败:" + err.message); } });
对应的Swift端代码可以大幅简化,不需要处理任何base64转码、上传Storage的逻辑,拿到返回的URL直接用即可。
方案2:保留前端转码上传逻辑,修复base64解码问题
如果一定要走前端转base64的流程,注意Data(base64Encoded:)对字符串格式要求极严:字符串长度必须是4的倍数、不能带换行/空格/转义字符等多余内容,必须先做清洗再解码。
Swift端解码正确写法
guard let res = result?.data as? String else { debugPrint("返回数据格式错误,不是字符串类型") return } // 1. 清洗所有换行、空格、转义字符 var cleanedBase64 = res.replacingOccurrences( of: "[\\s\\n\\r\\t\\\"]", with: "", options: .regularExpression ) // 2. 补全末尾缺失的=填充符,保证长度是4的倍数 let padCount = (4 - cleanedBase64.count % 4) % 4 cleanedBase64 += String(repeating: "=", count: padCount) // 3. 带忽略未知字符选项解码 guard let dataPDF = Data( base64Encoded: cleanedBase64, options: .ignoreUnknownCharacters ) else { debugPrint("base64解码失败,字符串前缀:\(cleanedBase64.prefix(100))") return } // 后续上传Storage的逻辑保持不变即可
同时Node端要确认response.data是原始Buffer类型,不要先转成utf8字符串再转base64,否则会直接损坏二进制内容。
额外避坑提醒
- 你代码里大量用强制解包
!,只要documentID、dataPDF、url任意一个值为nil,APP就会直接崩溃,全部改成guard let做安全校验 - 不建议用手机号作为文件名存储敏感资产报告,最好拼接随机非序列字符串,避免被恶意遍历下载
- 云函数端不要信任前端传的用户标识,优先从
context.auth.uid拿用户ID,避免用户篡改请求参数上传/下载其他人的报告
内容的提问来源于stack exchange,提问作者Nat Serrano
相关产品推荐
相关产品推荐

