返回博客

wx.openDocument「打开文件失败」:带查询参数的 OSS 链接吃掉了扩展名

在某个 C 端小程序里,用户点击「查看合同」按钮,页面先转圈——然后弹出「打开文件失败」。后台日志显示 wx.downloadFile 正常返回 statusCode: 200,文件字节数也对得上,按道理 PDF 应该能打开。但就是打不开。

更诡异的是:在微信开发者工具里跑完全没问题,换成真机就必现。这种"工具好、真机坏"的组合,排查起来格外消耗心力。

现象

wx.downloadFile({
  url: 'https://oss.example.com/contract.pdf?OSSAccessKeyId=xxx&Expires=1742025600&Signature=yyy',
  success(res) {
    // res.statusCode === 200,下载成功
    // res.tempFilePath === "http://tmp/wx_file_abc123"  ← 注意:没有扩展名
    wx.openDocument({
      filePath: res.tempFilePath,
      // 没有传 fileType
    });
    // 真机上:触发 fail 回调,toast "打开文件失败"
  }
});

下载成功,tempFilePath 有值,但 wx.openDocument 进了 fail 分支。

根因

wx.downloadFile 生成临时文件路径的规则是:用随机 ID 命名,不保留原始文件扩展名

当源 URL 是干净路径(如 https://cdn.example.com/file.pdf)时,某些版本的微信基础库会从 URL 路径中提取 .pdf 写入临时文件名,侥幸能用。但一旦 URL 带有查询参数(OSS 预签名 URL、CDN token 鉴权 URL 等),这个"顺手提取"的逻辑就会失效,临时文件落盘后没有任何扩展名

wx.openDocument 不做文件头 Magic Bytes 检测,完全依赖扩展名判断文件类型。扩展名缺失 → 类型未知 → 静默失败。

开发者工具之所以有时能正常打开,是因为 DevTools 的文件处理逻辑与真机基础库存在差异,这让 bug 在开发阶段完全隐身。

坑:OSS 签名 URL 的查询参数会让临时文件丢失扩展名

wx.downloadFile 生成的 tempFilePath 长这样:

http://tmp/wx_file_abc123   ← 无扩展名

wx.openDocument 拿到这个路径后无法确定文件类型,在真机上必然报错。微信开发者工具的文件处理与真机不一致,导致此 bug 在开发阶段难以发现。

正解

从原始 URL 里主动提取文件类型,通过 fileType 参数显式传给 wx.openDocument

// 从 URL 中提取文件类型(先去掉查询参数再取扩展名)
function getFileType(url) {
  if (!url) return '';
  const path = url.split('?')[0];
  const ext = path.split('.').pop().toLowerCase();
  const supported = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'];
  return supported.includes(ext) ? ext : '';
}
 
const fileUrl = 'https://oss.example.com/contract.pdf?OSSAccessKeyId=xxx&Expires=1742025600';
const fileType = getFileType(fileUrl);  // → 'pdf'
 
wx.downloadFile({
  url: fileUrl,
  success(res) {
    if (res.statusCode !== 200) return;
    const opts = {
      filePath: res.tempFilePath,
      showMenu: true,
      fail() {
        wx.showToast({ title: '打开文件失败', icon: 'none' });
      }
    };
    if (fileType) opts.fileType = fileType;  // 关键:显式传类型
    wx.openDocument(opts);
  }
});
正解:从原始 URL 解析 fileType 并显式传参

fileTypewx.openDocument可选但关键参数。当 tempFilePath 无扩展名时,不传就必崩。

解析逻辑:url.split('?')[0] 先剥离查询参数,再 .split('.').pop() 取扩展名,最后做白名单校验(wx.openDocument 支持:doc / docx / xls / xlsx / ppt / pptx / pdf)。

延伸

  • 如果文件 URL 完全没有扩展名(比如后端返回 /api/download/123 这种流式接口),前端无法从 URL 推断类型,需要后端在 API 响应里额外提供 fileType 字段。
  • 图片文件(jpg / png 等)不要用 wx.openDocument,走 wx.previewImage
  • 同样受影响:wx.saveFile——无扩展名的临时文件保存后,设备上的文件管理器也可能无法识别文件类型。

一句话外卖

调用 wx.openDocument 时,永远从原始 URL 解析 fileType 显式传参——不要依赖微信基础库从 tempFilePath 反推,尤其是 OSS 预签名 URL 这类带查询参数的场景。