Files
sgxt_web/docs/麒麟系统文件下载兼容性修复方案.md
2026-04-28 11:26:26 +08:00

6.5 KiB
Raw Blame History

麒麟系统浏览器文件下载兼容性修复方案

一、问题现象

下载成功,但文件打不开。文件已保存到本地,但用对应软件打开时提示损坏或格式错误。

二、根因分析

可能原因 1URL.revokeObjectURL 释放过早(文件内容损坏)

当前代码第300-313行

function downloadFile(url, filename) {
  fetch(url)
    .then((response) => response.blob())
    .then((blob) => {
      const link = document.createElement("a");
      link.href = URL.createObjectURL(blob);
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(link.href);  // 同步释放,可能过早
    })
}

link.click() 在麒麟浏览器中是异步的,同步调用 revokeObjectURL 会在 Blob 数据写入磁盘前就销毁它,导致文件内容不完整。


可能原因 2文件名无后缀 / 后缀被篡改

分析文件名传递链路:

上传时handlerSuccess 保存的是 { id, name }name 来自浏览器原始文件名(带后缀),这部分没问题。

但回显时watch 中第183-188行存在一个关键问题

// 当 modelValue 元素是字符串(非对象)时:
} else {
  return {
    url: String(`/mosty-api/mosty-base/minio/image/download/` + el || ""),
    id: el
    // ← 没有 name 属性!
  };
}

此时 file.nameundefined,传入 downloadFile(file.url, file.name)

link.download = undefined;  // 文件名丢失

麒麟浏览器的行为:当 download 属性为空或 undefined 时,浏览器会:

  • 从 URL 路径提取文件名(如 /minio/image/download/abc123 → 文件名变成 abc123无后缀
  • 或根据 Blob 的 type 自动添加后缀(如服务端返回的 Content-Type 是 application/json → 强制加 .json 后缀)

结果:一个 .docx 文件下载后变成了 .json 或无后缀文件,自然打不开。

验证方法:在麒麟系统上下载一个文件,查看下载后的文件名是否和原始文件名一致(包括后缀)。


可能原因 3麒麟系统安全机制拦截

麒麟系统(基于 Linux自带安全中心可能触发以下行为

安全机制 行为 结果
文件隔离 将下载的文件标记为不可信,移到隔离区 文件存在但被锁,其他程序无法读取
执行权限 给文件添加可执行标记,或删除可执行标记 程序拒绝打开带危险标记的文件
杀毒扫描 实时扫描下载文件,误报则隔离 文件被移动或内容被修改
WINE 兼容层 试图用 WINE 打开 Windows 格式文件 文件关联错误,打开方式不对

验证方法

  1. 在麒麟系统终端执行 ls -la 查看下载文件是否有特殊权限标记
  2. 检查 /tmp 或隔离区目录是否有被拦截的文件
  3. 暂时关闭麒麟安全中心,重新下载测试

三、修复方案

方案 A综合修复推荐

同时解决原因1和原因2并在下载失败时给出明确提示

import { saveAs } from 'file-saver'

// 补全文件名后缀
function ensureFilename(file) {
  if (file.name) return file.name
  // name 丢失时,从 URL 中尝试提取,或使用默认名
  const urlId = file.url?.split('/').pop()
  return urlId ? `文件_${urlId}` : '未命名文件'
}

function downloadFile(url, filename) {
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`下载失败: ${response.status}`)
      }
      return response.blob()
    })
    .then((blob) => {
      // saveAs 内部处理了 Blob 释放时序,不会过早 revoke
      saveAs(blob, filename)
    })
    .catch((error) => {
      console.error('下载失败:', error)
      ElMessage.error('文件下载失败,请重试')
    })
}

const handleDownload = (file) => {
  if (file?.response?.data) {
    window.open(file.response.data)
  } else if (file?.url) {
    const filename = ensureFilename(file)
    downloadFile(file.url, filename)
  }
}

改动内容

  1. 引入 file-saver(项目已有依赖)→ 解决 Blob 释放过早
  2. ensureFilename 补全文件名 → 解决后缀丢失
  3. 响应状态校验 → 发现服务端错误时提示用户

方案 B仅修复 Blob 释放时序(最小改动)

function downloadFile(url, filename) {
  fetch(url)
    .then((response) => response.blob())
    .then((blob) => {
      const blobUrl = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = blobUrl;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      // 延迟释放,确保浏览器完成 Blob 数据拷贝
      setTimeout(() => URL.revokeObjectURL(blobUrl), 3000);
    })
    .catch((error) => console.error("下载失败:", error));
}

改动量1 行。但未修复文件名丢失问题。


方案 Cwindow.open 直接下载

const handleDownload = (file) => {
  if (file?.url) {
    window.open(file.url, "_blank");
  }
};

前提:后端接口需设置 Content-Disposition: attachment; filename="xxx.docx" 响应头,浏览器才能正确处理文件名和下载行为。


四、排查步骤

建议按以下顺序验证,定位到底是哪个原因:

  1. 检查文件名:在麒麟系统下载后,文件名是否带正确后缀(如 .docx.pdf

    • 后缀丢失/被改 → 原因2文件名问题
    • 后缀正确 → 排除原因2
  2. 检查文件大小:下载的文件大小是否和服务端一致?

    • 文件明显偏小或0字节 → 原因1Blob 释放过早)
    • 大小一致 → 排除原因1
  3. 关闭安全中心测试:暂时关闭麒麟安全中心,重新下载

    • 能正常打开 → 原因3安全拦截
    • 仍打不开 → 排除原因3

五、方案对比

方案 A 综合修复 方案 B 延迟释放 方案 C window.open
修复原因1Blob释放 不涉及
修复原因2文件名后缀 取决于后端
处理原因3安全拦截 需系统配置
改动量 ~15行 1行 最小

建议先执行排查步骤确认根因再选择对应方案。如果原因1和2同时存在直接用方案A。


文档版本: v3.0 创建日期: 2026-04-24