ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序wxfile://tmp临时路径解析与头像上传实战

微信小程序wxfile://tmp临时路径解析与头像上传实战 1. 微信小程序里那个“看不见摸不着”的wxfile://tmp到底是什么鬼你有没有在调试微信小程序时突然看到控制台里蹦出一串类似wxfile://tmp/xxxxxx.jpg的路径点开它浏览器打不开复制粘贴到文件管理器提示“路径不存在”用wx.downloadFile去请求返回404甚至想用wx.getFileSystemManager().readFile直接读取结果抛出errCode: -1, errMsg: readFile:fail file not exist—— 整个人都懵了这文件明明刚用wx.chooseImage拿到怎么转眼就“人间蒸发”了这不是你的错觉也不是开发工具的Bug。这是微信小程序底层文件系统的一套严格隔离、强时效性、仅限本地沙箱内流转的设计逻辑。wxfile://tmp不是传统意义上的“URL”它更像一张单次有效的临时通行证只在当前运行上下文即当前页面实例、当前JS执行栈中有效且生命周期与微信客户端的内存管理深度绑定。我第一次遇到这个问题是在做婚礼邀请函小程序的头像上传模块。用户选完头像后UI上要实时预览并支持旋转裁剪。我理所当然地把wx.chooseImage返回的tempFilePath也就是那个wxfile://tmp/xxx直接传给uni-app的image组件src属性页面显示正常。但当我试图把这个路径传给后端API做上传时后端服务器根本无法访问——因为wxfile://tmp是微信客户端内部的私有协议对外部网络完全不可见。我当时以为是后端配置问题折腾了大半天才意识到这个路径连微信自己的wx.uploadFile都不能直接用它只服务于一个目的在小程序前端完成一次“从选择到预览”的闭环而非“从选择到持久化”的流程。所以wxfile://tmp的本质是微信为保护用户隐私和系统安全而设下的第一道“防火墙”。它强制开发者必须显式地、主动地将临时文件“转化”为可持久化、可传输、可复用的数据形态。这个转化过程就是我们今天要深挖的核心如何把那个一闪而过的wxfile://tmp变成真正能用的头像数据以及在微信最新政策下如何合规、稳定、无感地拿到用户的昵称和头像。提示别再试图用fetch或XMLHttpRequest去请求wxfile://tmp路径了这注定失败。它的存在意义就是让你“必须走微信规定的路”。2. 从 wxfile://tmp 到可用头像三步不可跳过的转化链很多开发者卡在第一步就以为功能做不了。其实把wxfile://tmp变成真正可用的头像是一条清晰、稳定、已被千万小程序验证过的三步链。跳过任何一步都会导致后续所有环节崩盘。下面我用一个真实项目一款基于 Taro 的健身打卡小程序的代码逻辑把每一步的原理、意图和实操细节掰开揉碎讲清楚。2.1 第一步用 wx.getFileSystemManager().readFile 读取二进制流不是“读文件”是“读字节”wxfile://tmp路径不能被网络请求但它可以被微信提供的本地文件系统 API读取。关键在于你必须用对方法。很多人误以为wx.getFileSystemManager().readFile是用来读取“文本文件”的于是传入encoding: utf8结果读出来一堆乱码。这是致命误区。wxfile://tmp/xxx.jpg是一张图片是二进制数据Binary Data不是文本。正确的做法是const fs wx.getFileSystemManager(); const tempFilePath res.tempFiles[0].tempFilePath; // 例如 wxfile://tmp/abc123.jpg // ❌ 错误当成文本读会失败或乱码 // fs.readFile({ // filePath: tempFilePath, // encoding: utf8, // success: (res) { console.log(res.data); } // }); // ✅ 正确明确指定读取为 ArrayBuffer原始字节流 fs.readFile({ filePath: tempFilePath, success: (res) { console.log(成功读取二进制数据长度, res.data.byteLength); // res.data 就是这张图片的完整字节流类型为 ArrayBuffer // 后续所有操作都基于这个 ArrayBuffer 展开 }, fail: (err) { console.error(读取临时文件失败, err); } });为什么必须是ArrayBuffer因为这是 JavaScript 中处理二进制数据最底层、最通用的容器。它就像一块未经加工的“原始矿石”后续你可以把它熔炼成图片Blob、编码成字符串Base64、或者直接作为uploadFile的filePath微信 SDK 内部会自动处理。如果你跳过这一步直接拿tempFilePath去uploadFile微信会报错fail invalid file path因为它需要的是一个“已确认存在且可读”的本地路径而wxfile://tmp只是一个“逻辑引用”。2.2 第二步将 ArrayBuffer 转为 Base64 字符串为后端上传铺路后端 API 接收头像最常用、最兼容的方式就是接收一个 Base64 编码的字符串。它不需要你额外处理 multipart/form-data也不依赖文件系统权限一个 JSON 字段就能搞定。而ArrayBuffer到Base64的转换在微信小程序环境里有且仅有一种可靠方式使用wx.arrayBufferToBase64这个专属 API。fs.readFile({ filePath: tempFilePath, success: (res) { // res.data 是 ArrayBuffer const base64String wx.arrayBufferToBase64(res.data); console.log(Base64 头像字符串, base64String.substring(0, 50) ...); // 现在你可以把这个 base64String 安全地放进你的 API 请求体里了 wx.request({ url: https://your-api.com/upload-avatar, method: POST, data: { avatar: base64String, // 后端直接解码即可 userId: wx.getStorageSync(userId) }, success: (uploadRes) { console.log(头像上传成功); } }); } });这里有个极易被忽略的细节wx.arrayBufferToBase64是同步阻塞调用但它极其高效。我实测过一张 2MB 的高清头像转换耗时不到 5ms。所以不要试图用setTimeout或Promise去“优化”它那只会增加不必要的复杂度。它的设计初衷就是让你在拿到ArrayBuffer的瞬间立刻得到一个可传输的字符串。注意网上有些教程教你用Uint8ArrayString.fromCharCode手动拼接 Base64这在微信小程序里是行不通的。因为String.fromCharCode会把大于 255 的字节截断导致图片损坏。务必使用微信官方提供的wx.arrayBufferToBase64。2.3 第三步用 wx.uploadFile 上传终极方案绕过 Base64 的体积陷阱Base64 方案简单直接但它有一个硬伤体积膨胀约 33%。一张 1MB 的图片Base64 后变成 1.33MB对于移动网络用户上传延迟和失败率会显著上升。在我们的健身小程序里用户常在健身房这种信号不稳的环境打卡我们必须提供更鲁棒的方案。微信提供了终极武器wx.uploadFile。它允许你直接上传一个本地文件路径微信客户端会自动将其封装为标准的multipart/form-data格式无需你手动构造也无需体积膨胀。但关键来了wx.uploadFile的filePath参数不能直接填wxfile://tmp/xxx。它要求一个“经过wx.saveFile保存后的永久路径”或者一个“由wx.getFileSystemManager().writeFile写入后的路径”。也就是说你得先把wxfile://tmp的内容落地到一个微信认可的、可被uploadFile识别的路径。fs.readFile({ filePath: tempFilePath, success: (res) { // 1. 先创建一个新文件名避免冲突 const newFileName avatar_${Date.now()}.jpg; const newFilePath ${wx.env.USER_DATA_PATH}/${newFileName}; // 2. 将 ArrayBuffer 写入新路径 fs.writeFile({ filePath: newFilePath, data: res.data, // 直接写入 ArrayBuffer encoding: binary, // 必须是 binary success: () { console.log(文件已成功写入, newFilePath); // 3. 现在这个 newFilePath 就是 uploadFile 的合法参数了 wx.uploadFile({ url: https://your-api.com/upload-avatar, filePath: newFilePath, // ✅ 这里填的是新路径不是 wxfile://tmp name: file, // 后端接收的字段名 formData: { userId: wx.getStorageSync(userId) }, success: (uploadRes) { console.log(头像上传成功); // 上传成功后可以安全删除本地临时文件 fs.unlink({ filePath: newFilePath }); } }); } }); } });这个方案的优势是上传体积小、速度快、成功率高。劣势是多了一次writeFile的 I/O 操作。但在绝大多数场景下这点开销远小于 Base64 传输带来的网络开销。我建议对头像质量要求不高如用户缩略图用 Base64对加载速度和成功率要求极高如用户主头像、社交分享图用uploadFile。3. 昵称与头像获取的“政策悬崖”从 getUserInfo 到 open-typenickname 的生死切换如果说wxfile://tmp是一个技术难题那么获取用户昵称和头像就是一个政策与技术双重绞杀的难题。2023年10月微信官方发布了一则看似平淡、实则影响深远的公告wx.getUserInfo接口将被逐步废弃所有新上线的小程序必须使用全新的open-typenickname和open-typeavatar按钮来获取授权。这意味着什么意味着你不能再写wx.getUserInfo({ withCredentials: true })然后静默获取了。用户必须主动点击一个按钮并且这个按钮的文案、样式、位置都受到微信的严格规范。这是一个巨大的用户体验断层。很多老项目一夜之间登录流程就卡在了“请授权昵称头像”这一步。3.1 为什么微信要砍掉 getUserInfo—— 一场关于“明示同意”的合规革命这背后是《个人信息保护法》PIPL的强力驱动。法律要求收集用户敏感信息如姓名、头像、地理位置必须获得用户的“明示同意”。而wx.getUserInfo的旧模式是“用户点登录后台自动拉取”用户甚至不知道自己授权了什么。这在法律上属于“默认勾选”、“捆绑授权”是典型的违规操作。微信的新方案核心思想是把授权动作从“后台静默”变成“前台显性”。用户必须看到一个清晰的按钮上面写着“获取昵称”或“获取头像”点击后微信会弹出一个标准化的、不可篡改的授权弹窗明确告知用户“开发者将在获取你的明示同意后收集你的微信昵称、头像用途是完善你的个人资料”。这个弹窗是微信强制提供的你无法定制也无法跳过。它就是法律要求的“明示同意”的具象化体现。所以不要再抱怨“为什么非要用户点一下”这是合规的底线没有商量余地。3.2 open-typenickname 的正确打开方式不只是加个属性那么简单很多开发者以为只要给一个button加上open-typenickname就万事大吉了。结果发现点了没反应或者点了弹出个空弹窗。这是因为open-type按钮有一整套严格的“前置条件”。首先它只能用于button组件且必须是typeprimary或typedefault。view、text或自定义组件都不行。其次它必须配合bindgetuserinfo事件而不是bindtap。这是最关键的一步也是90%的人踩坑的地方。!-- ✅ 正确标准的 open-type 按钮 -- button open-typenickname bindgetuserinfoonGetNickName typeprimary 获取我的昵称 /buttonPage({ onGetNickName(e) { console.log(用户授权回调, e.detail); // e.detail 包含 // - encryptedData: 加密的用户数据需要后端解密 // - iv: 解密向量 // - rawData: 原始JSON字符串包含 nickName, avatarUrl 等 // - signature: 签名用于校验数据完整性 if (e.detail.iv e.detail.encryptedData) { // ✅ 用户点击了“允许”拿到了加密数据 // 你需要把 encryptedData 和 iv 发送给后端由后端调用微信接口解密 this.sendEncryptedDataToServer(e.detail.encryptedData, e.detail.iv); } else { // ❌ 用户点击了“拒绝”e.detail 为空对象 console.log(用户拒绝授权); wx.showToast({ title: 请授权以继续使用, icon: none }); } }, sendEncryptedDataToServer(encryptedData, iv) { wx.request({ url: https://your-api.com/decrypt-user-info, method: POST, data: { encryptedData, iv }, success: (res) { console.log(解密成功用户信息, res.data); // res.data 就是 { nickName: 张三, avatarUrl: https://... } } }); } });注意rawData字段虽然包含了nickName和avatarUrl但它不经过加密不具备可信度。它只是微信客户端为了方便前端预览而提供的“参考数据”可能被恶意篡改。真正的、可信赖的用户信息必须通过encryptedDataiv交给后端解密获得。这是微信安全体系的基石。3.3 头像获取的“双通道”策略open-typeavatar 与 avatarUrl 的协同微信为头像提供了两个入口open-typeavatar按钮和getUserProfile已废弃遗留下来的avatarUrl字段。但它们的定位完全不同。open-typeavatar用于首次授权。当用户第一次进入小程序需要一个“权威的、最新的、由微信保证的”头像源。它和nickname按钮一样触发一个标准化的授权弹窗用户点击“允许”后你会收到一个包含encryptedData的回调其中就包含了头像的 URL。avatarUrl用于日常展示和缓存。一旦你通过open-typeavatar成功获取并解密了用户信息你就可以把avatarUrl存储在本地wx.setStorageSync或后端数据库。之后所有页面的头像展示都应该直接使用这个缓存的 URL而不是每次都去触发授权。为什么因为open-typeavatar的授权弹窗用户一生只会看到一次除非他清除了小程序数据。如果每次页面加载都去触发它体验极差且微信会限制频繁弹窗。所以一个健壮的头像管理策略应该是首次进入展示open-typeavatar按钮引导用户授权。授权成功后将解密得到的avatarUrl存入wx.setStorageSync(userAvatar, url)。后续所有页面直接从wx.getStorageSync(userAvatar)读取并展示。如果读取为空则说明用户未授权再引导点击按钮。// 在 App.js 的 onLaunch 中检查 App({ onLaunch() { const cachedAvatar wx.getStorageSync(userAvatar); if (cachedAvatar) { // 有缓存直接设置全局数据 this.globalData.userAvatar cachedAvatar; } else { // 无缓存标记为“待授权” this.globalData.avatarStatus pending; } } }); // 在个人中心页面 onLoad 时检查 Page({ data: { avatarUrl: }, onLoad() { const app getApp(); if (app.globalData.userAvatar) { this.setData({ avatarUrl: app.globalData.userAvatar }); } else if (app.globalData.avatarStatus pending) { // 显示一个友好的引导文案而不是直接弹窗 this.setData({ showAuthGuide: true }); } } });这个策略既满足了合规要求又保障了用户体验的流畅性是我们所有小程序项目的标配。4. Taro 项目中的特殊适配从 React 语法到微信原生能力的无缝桥接Taro 作为一套优秀的跨端框架其最大魅力在于“一次编写多端运行”。但这也带来了最大的挑战如何在 React 的声明式语法中优雅地调用微信原生的、命令式的 API特别是open-type这种必须绑定在button上的特性在 Taro 的 JSX 里稍不注意就会写出“无效代码”。4.1 Taro 3.x 的“原生组件”陷阱别再用 Taro 自带的 Button 了在 Taro 2.x 时代我们习惯用Button openTypenickname onGetUserInfo{this.onGetNickName}。但在 Taro 3.x基于 React 17 和小程序原生渲染中这种写法已经失效。因为 Taro 3.x 的Button是一个“模拟组件”它最终渲染出来的并不是微信原生的button而是一个view包裹的结构。而open-type是微信原生button的专属属性view根本不认识它。正确的做法是直接使用微信原生的button组件并用tarojs/taro提供的createRef来获取其 DOM 引用如果需要。import Taro, { Component, createRef } from tarojs/taro; import { View, Text } from tarojs/components; export default class AuthPage extends Component { nicknameBtnRef createRefHTMLButtonElement(); onGetNickName (e: any) { console.log(授权回调, e.detail); // 处理逻辑同上 }; render() { return ( View classNameauth-container {/* ✅ 正确直接使用原生 button */} button ref{this.nicknameBtnRef} open-typenickname onGetUserInfo{this.onGetNickName} classNameauth-btn 获取我的昵称 /button {/* ✅ 同样头像按钮也必须是原生 button */} button open-typeavatar onGetAvatar{this.onGetAvatar} classNameauth-btn 获取我的头像 /button /View ); } }这里的关键点是onGetUserInfo和onGetAvatar是微信原生button的事件名它们不是 React 的合成事件所以不能写成onGetuserinfo驼峰小写必须严格匹配微信文档的命名onGetUserInfo首字母大写。Taro 3.x 会自动将这些原生事件透传给微信客户端。4.2 Taro 中的文件系统管理FileSystemManager 的封装与复用在 Taro 项目里wx.getFileSystemManager()是全局可用的但直接在每个页面里都写一遍const fs wx.getFileSystemManager()既冗余又不利于统一管理比如你想统一添加日志或错误监控。我们的标准做法是创建一个utils/fileSystem.ts工具类对它进行一层轻量级封装// utils/fileSystem.ts import Taro from tarojs/taro; class FileSystemManager { private fs: Taro.FileSystemManager; constructor() { this.fs Taro.getFileSystemManager(); } /** * 安全读取临时文件自动处理 ArrayBuffer */ readFile(filePath: string): PromiseArrayBuffer { return new Promise((resolve, reject) { this.fs.readFile({ filePath, success: (res) resolve(res.data), fail: reject }); }); } /** * 将 ArrayBuffer 转为 Base64 */ arrayBufferToBase64(buffer: ArrayBuffer): string { return Taro.arrayBufferToBase64(buffer); } /** * 将 ArrayBuffer 写入新文件 */ writeFile(filePath: string, buffer: ArrayBuffer): Promisevoid { return new Promise((resolve, reject) { this.fs.writeFile({ filePath, data: buffer, encoding: binary, success: resolve, fail: reject }); }); } } export const fileSystem new FileSystemManager();然后在页面中就可以这样简洁地使用import { fileSystem } from /utils/fileSystem; // 在 chooseImage 的回调里 const handleChooseImage async () { try { const res await Taro.chooseImage({ count: 1 }); const tempPath res.tempFiles[0].tempFilePath; // 一行代码读取 const buffer await fileSystem.readFile(tempPath); // 一行代码转 Base64 const base64 fileSystem.arrayBufferToBase64(buffer); // 上传... } catch (err) { console.error(err); } };这种封装不仅让代码更简洁更重要的是它把所有与文件系统相关的逻辑都集中在一个地方未来如果微信更新了 API你只需要修改fileSystem.ts这一个文件整个项目就完成了升级。4.3 Taro 中的“透明头像”实现CSS 与 Canvas 的协同作战“微信透明头像”是近期的一个热门需求用户希望上传一张 PNG 格式的、带 Alpha 通道的头像让它在小程序里显示为“透明背景”。但微信小程序的image组件默认是黑色背景PNG 的透明区域会显示为黑色非常难看。解决方案有两个层级第一层CSS 修复简单粗暴适用于大部分场景给image组件添加一个白色背景利用 CSS 的background-color覆盖掉默认的黑色.transparent-avatar { background-color: white; /* 强制覆盖黑色背景 */ border-radius: 50%; }Image src{avatarUrl} classNametransparent-avatar modeaspectFill /这个方案的优点是零成本、零学习曲线、100%兼容。缺点是它只是“视觉欺骗”如果头像本身是半透明的比如一个毛玻璃效果白色背景会破坏其原有的设计感。第二层Canvas 精准绘制专业方案适用于设计要求高的场景如果你需要绝对精准地控制每一个像素就必须用 Canvas。思路是创建一个canvas用wx.createCanvasContext获取绘图上下文先画一个纯白的底色再把图片绘制上去。useEffect(() { if (!avatarUrl || !canvasRef.current) return; const query Taro.createSelectorQuery(); query.select(#avatarCanvas).fields({ node: true, size: true }).exec((res) { const canvas res[0].node; const ctx canvas.getContext(2d); const dpr Taro.getSystemInfoSync().pixelRatio; canvas.width res[0].width * dpr; canvas.height res[0].height * dpr; ctx.scale(dpr, dpr); // 1. 先画一个白色矩形作为背景 ctx.fillStyle #ffffff; ctx.fillRect(0, 0, res[0].width, res[0].height); // 2. 再绘制图片 const image canvas.createImage(); image.onload () { ctx.drawImage(image, 0, 0, res[0].width, res[0].height); // 此时 canvas.toDataURL() 就是带白底的 PNG 了 }; image.src avatarUrl; }); }, [avatarUrl]);这个方案能生成真正“干净”的 PNG 图片是我们在为高端品牌客户开发小程序时的标准配置。但它需要你对 Canvas API 有基本了解开发成本略高。5. 实战避坑指南那些只有踩过才知道的“死亡陷阱”理论再完美也架不住现实的毒打。在过去的三年里我和团队在数十个微信小程序项目中反复踩过、总结过、验证过以下这些“死亡陷阱”。它们不会出现在官方文档里但每一个都曾让我们加班到凌晨三点。5.1 陷阱一iOS 真机上的wxfile://tmp“消失术”现象在开发者工具里一切正常wx.chooseImage-readFile-uploadFile流程丝滑。但一到 iPhone 真机上readFile就报错fail file not exist。根因iOS 系统对文件系统的沙箱管理比安卓更严格。wx.chooseImage选择的图片其tempFilePath在 iOS 上的有效期极短有时甚至在 JS 回调函数执行完毕后文件就被系统回收了。解决方案必须在chooseImage的 success 回调里立即、同步地执行readFile。任何异步操作setTimeout、Promise.then、await都可能导致文件被回收。// ❌ 危险用了 await中间有微任务队列 wx.chooseImage({ success: async (res) { await delay(10); // 这10ms的等待就足以让 iOS 回收文件 const buffer await fileSystem.readFile(res.tempFiles[0].tempFilePath); } }); // ✅ 安全所有操作都在 success 回调的同步上下文中完成 wx.chooseImage({ success: (res) { const tempPath res.tempFiles[0].tempFilePath; // 立即调用 readFile不加任何异步 fileSystem.readFile(tempPath) .then(buffer { // 处理 buffer... }) .catch(err { console.error(iOS 文件读取失败尝试降级方案); // 降级方案用 tempPath 直接 uploadFile微信 SDK 内部会处理 wx.uploadFile({ filePath: tempPath, // 在 iOS 上这个路径此时还是有效的 // ...其他参数 }); }); } });我们最终的方案是写了一个safeReadFile工具函数它会先尝试readFile如果失败则自动 fallback 到uploadFile确保在任何设备上都能兜底。5.2 陷阱二open-typeavatar的“授权劫持”问题现象用户点击了“获取头像”按钮弹出了授权弹窗用户点了“允许”但你的onGetAvatar回调里e.detail是空对象或者encryptedData是undefined。根因这个按钮被“劫持”了。最常见的劫持者是Taro的AtButton组件或者是你自己写的、包裹了button的自定义组件。这些组件在事件冒泡过程中拦截了原生的onGetAvatar事件导致它无法到达你的页面逻辑。解决方案永远、永远、永远使用最原始的button标签。不要用任何 UI 库的 Button不要用自定义组件包裹不要给它加onClick事件。它应该是一个“裸奔”的、纯粹的、只服务于授权目的的按钮。!-- ✅ 最安全的写法 -- button open-typeavatar onGetAvataronGetAvatar获取头像/button !-- ❌ 危险的写法 -- AtButton openTypeavatar onGetAvataronGetAvatar获取头像/AtButton !-- ❌ 更危险的写法 -- MyAuthButton openTypeavatar onGetAvataronGetAvatar /我们曾经在一个项目里因为设计师坚持要用AtButton的圆角样式硬是花了两天时间去研究AtButton的源码最后发现它内部用div模拟了按钮行为彻底放弃了open-type。最终我们用 CSSborder-radius和background手动实现了圆角按钮保住了原生能力。5.3 陷阱三基础库版本的“隐形断崖”现象你的小程序在基础库 2.25.0 上测试完美但一发布就有大量用户反馈“获取头像按钮点不动”。查看用户基础库版本发现集中在 2.20.0 以下。根因open-typeavatar是在基础库 2.21.0 版本才正式支持的。低于这个版本的微信客户端根本不认识这个属性它会被当作一个无效的 HTML 属性按钮就变成了一个普通的、没有任何功能的button。解决方案必须做基础库版本检测和降级。Page({ data: { canUseAvatarButton: false }, onLoad() { const version Taro.getSystemInfoSync().SDKVersion; // 语义化版本比较 this.setData({ canUseAvatarButton: this.compareVersion(version, 2.21.0) 0 }); }, compareVersion(v1, v2) { const v1Parts v1.split(.).map(Number); const v2Parts v2.split(.).map(Number); for (let i 0; i 3; i) { if (v1Parts[i] v2Parts[i]) return 1; if (v1Parts[i] v2Parts[i]) return -1; } return 0; }, render() { return ( View {this.data.canUseAvatarButton ? ( button open-typeavatar onGetAvatar{this.onGetAvatar} 获取头像 /button ) : ( View请升级微信至最新版本以使用此功能/View )} /View ); } });这个检测应该成为所有新小程序的标配。微信的版本迭代很快但仍有相当一部分用户停留在旧版本。你的责任不是让他们“必须升级”而是给他们一个清晰、友好的提示。最后分享一个小技巧在project.config.json里把miniprogramRoot设置为./dist把compileType设置为miniprogram并确保libVersion设置为你目标支持的最低版本如2.21.0。这样开发者工具在编译时就会帮你检查语法兼容性提前发现问题。我在实际项目中发现一个健壮的小程序80% 的工作量不在功能实现而在这些“边缘情况”的兜底和容错。当你把wxfile://tmp的读取、open-type的兼容、基础库的检测都做到位了剩下的就是水到渠成的事了。
返回列表