欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_node_vue_ts

本文完整梳理了ARM64架构鸿蒙PC(HarmonyOS/OpenHarmony6.1及以上),基于CodeArts IDE搭建Vite+Vue前端项目的全流程与疑难解决方案。项目实操中,Vite启动会出现rolldown原生模块权限拒绝报错,根源是鸿蒙系统拦截未签名二进制文件,最终解决方案为引入ohos-signpost自动签名工具,配置npm后置钩子,在依赖安装完成后自动为所有.node文件添加系统合法签名,消除权限校验拦截,最终实现CodeArts IDE内Vue+TS项目正常启动、调试。

这里有一篇比较详细的攻略:

OpenHarmony 鸿蒙 PC + CodeArts IDE 前端 Vite+Vue 完整开发环境搭建指南


一、file-type 库介绍

1. 作用

file-type 用于识别文件真实MIME类型、文件后缀,不靠文件名后缀判断,而是读取文件二进制头部特征(文件魔数 magic number)。
原生Node仅能通过后缀判断文件,容易被篡改后缀欺骗;file-type 读取文件二进制头部精准识别图片、视频、压缩包、文档、字体、音频等百余种文件格式。

核心能力

  1. 仅读取文件头部少量字节,无需完整加载大文件,性能高;
  2. 支持识别:图片(png/jpg/webp)、视频(mp4/mov)、压缩包(zip/7z/gz)、文档(pdf/docx/xlsx)、字体、音频、exe等;
  3. 支持传入本地文件路径、文件Buffer、文件Readable流三种方式检测;
  4. 同步+异步API,兼容脚本、后端上传校验场景;
  5. 可判断是否为图片、压缩包、文档,内置类型分组工具;
  6. 纯JS无二进制依赖,鸿蒙PC Node无需签名,无权限报错。

业务场景

  1. 后端用户上传文件校验:防止篡改后缀上传恶意文件;
  2. 文件管理器自动识别文件图标类型;
  3. 批量脚本扫描目录,分类整理图片/视频/压缩包;
  4. 解压zip后校验内部文件真实格式;
  5. 接口返回文件真实MIME,正确设置Content-Type响应头。

安装命令

npm i file-type
# TS类型提示
npm i -D @types/file-type

请添加图片描述

二、完整大篇幅示例代码 fileTypeDemo.js

const { fileTypeFromFile, fileTypeFromBuffer } = require('file-type');
const fs = require('fs-extra');
const path = require('path');
const { Readable } = require('stream');

// 全局测试工作目录
const WORKSPACE = path.resolve(__dirname, './fileTypeTestSpace');
// 各类测试素材路径
const TEST_FILES = {
  jpg: path.join(WORKSPACE, 'demo.jpg'),
  png: path.join(WORKSPACE, 'demo.png'),
  zip: path.join(WORKSPACE, 'bundle.zip'),
  pdf: path.join(WORKSPACE, 'doc.pdf'),
  mp4: path.join(WORKSPACE, 'video.mp4'),
  fakeJpg: path.join(WORKSPACE, 'fake.jpg'), // 实际是zip,后缀篡改
  empty: path.join(WORKSPACE, 'empty.txt'),
};

/**
 * 前置:自动生成各类测试文件,包含篡改后缀的欺骗文件
 */
async function initTestAssets() {
  await fs.ensureDir(WORKSPACE);
  // 批量创建空文件占位
  for (const fp of Object.values(TEST_FILES)) {
    await fs.ensureFile(fp);
  }
  // 写入zip魔数二进制头部到fake.jpg,模拟篡改后缀恶意文件
  const zipHeader = Buffer.from([80, 75, 3, 4]);
  await fs.writeFile(TEST_FILES.fakeJpg, zipHeader);
  console.log('✅ 测试素材文件自动创建完成(包含篡改后缀的fake文件)\n');
}

/**
 * 辅助打印文件识别结果
 * @param {string} filePath 文件路径
 * @param {import('file-type').FileTypeResult} result 识别结果
 */
function printTypeResult(filePath, result) {
  const fileName = path.basename(filePath);
  console.log(`文件:${fileName}`);
  if (!result) {
    console.log('  → 无法识别该文件类型\n');
    return;
  }
  console.log(`  MIME:${result.mime}`);
  console.log(`  后缀:.${result.ext}\n`);
}

/**
 * 工具:Node环境兼容读取文件头部Buffer(替代fileTypeFromStream)
 * @param {string} filePath 文件路径
 * @param {number} readSize 仅读取头部字节,默认4100(足够识别绝大多数文件魔数)
 */
async function getFileHeadBuffer(filePath, readSize = 4100) {
  const fd = await fs.open(filePath, 'r');
  const buf = Buffer.alloc(readSize);
  await fs.read(fd, buf, 0, readSize, 0);
  await fs.close(fd);
  return buf;
}

/**
 * 示例1:异步通过文件路径直接识别 fileTypeFromFile(最常用)
 */
async function demoDetectByFilePath() {
  console.log('===== 示例1:通过本地文件路径识别真实文件类型 =====');
  for (const filePath of Object.values(TEST_FILES)) {
    const type = await fileTypeFromFile(filePath);
    printTypeResult(filePath, type);
  }
}

/**
 * 示例2:传入完整文件二进制Buffer识别 fileTypeFromBuffer
 */
async function demoDetectByBuffer() {
  console.log('===== 示例2:读取文件完整Buffer,二进制识别类型 =====');
  const target = TEST_FILES.fakeJpg;
  const buf = await fs.readFile(target);
  const type = await fileTypeFromBuffer(buf);
  printTypeResult(target, type);
  console.log('结论:虽然后缀是jpg,但二进制识别为压缩包zip,可拦截恶意上传\n');
}

/**
 * 示例3:超大文件低内存方案,仅读取文件头部少量字节识别(Node兼容替代流API)
 */
async function demoLargeFileHeadDetect() {
  console.log('===== 示例3:超大文件低内存识别,仅读取文件头部4100字节 =====');
  const target = TEST_FILES.mp4;
  // 只读取头部,不加载整个大文件
  const headBuf = await getFileHeadBuffer(target, 4100);
  const type = await fileTypeFromBuffer(headBuf);
  printTypeResult(target, type);
  console.log('优势:GB级视频/镜像仅读取头部,无内存溢出风险\n');
}

/**
 * 示例4:自定义内存二进制Buffer检测,无本地文件
 */
async function demoCustomMemoryBuffer() {
  console.log('===== 示例4:纯内存二进制Buffer检测,无需本地磁盘文件 =====');
  // 构造zip魔数内存二进制
  const zipBuf = Buffer.from([80, 75, 3, 4, 0, 0, 0, 0]);
  const type = await fileTypeFromBuffer(zipBuf);
  console.log('内存二进制识别结果:');
  console.log(`  MIME:${type.mime} | 后缀:.${type.ext}\n`);
}

/**
 * 示例5:批量遍历整个目录,自动识别全部文件真实类型
 */
async function demoScanWholeDir() {
  console.log('===== 示例5:批量扫描整个目录,批量识别所有文件 =====');
  const entries = await fs.readdir(WORKSPACE, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.isDirectory()) continue;
    const fullPath = path.join(WORKSPACE, entry.name);
    const type = await fileTypeFromFile(fullPath);
    printTypeResult(fullPath, type);
  }
}

/**
 * 示例6:后端上传安全校验:仅允许真实图片文件,拦截篡改后缀文件
 */
async function demoImageUploadCheck() {
  console.log('===== 示例6:后端上传校验:仅允许真实图片文件 =====');
  const uploadFiles = [TEST_FILES.png, TEST_FILES.fakeJpg, TEST_FILES.mp4];
  for (const fp of uploadFiles) {
    const type = await fileTypeFromFile(fp);
    const fileName = path.basename(fp);
    const isImage = type?.mime?.startsWith('image/');
    if (isImage) {
      console.log(`${fileName} 合法图片,允许上传`);
    } else {
      console.log(`${fileName} 不是真实图片,拦截上传`);
    }
  }
  console.log('');
}

/**
 * 示例7:按文件大类自动分类(压缩包/文档/媒体/未知)
 */
async function demoFileClassify() {
  console.log('===== 示例7:按文件大类自动分类(压缩/文档/媒体) =====');
  const allFilePaths = Object.values(TEST_FILES);
  const group = {
    archive: [],
    document: [],
    media: [],
    unknown: [],
  };
  for (const fp of allFilePaths) {
    const type = await fileTypeFromFile(fp);
    const name = path.basename(fp);
    if (!type) {
      group.unknown.push(name);
      continue;
    }
    const mime = type.mime;
    if (['application/zip', 'application/gzip', 'application/x-7z-compressed'].includes(mime)) {
      group.archive.push(name);
    } else if (mime.startsWith('application/pdf') || mime.includes('officedocument')) {
      group.document.push(name);
    } else if (mime.startsWith('image/') || mime.startsWith('video/') || mime.startsWith('audio/')) {
      group.media.push(name);
    } else {
      group.unknown.push(name);
    }
  }
  console.log('文件分类结果:');
  console.log('压缩包:', group.archive);
  console.log('文档:', group.document);
  console.log('媒体文件:', group.media);
  console.log('无法识别:', group.unknown, '\n');
}

/**
 * 统一入口执行全部演示
 */
async function runAllDemo() {
  // 1. 生成测试素材
  await initTestAssets();

  // 2. 依次执行所有兼容Node的示例
  await demoDetectByFilePath();
  await demoDetectByBuffer();
  await demoLargeFileHeadDetect();
  await demoCustomMemoryBuffer();
  await demoScanWholeDir();
  await demoImageUploadCheck();
  await demoFileClassify();

  console.log('🎉 file-type 全部功能示例执行完毕');
}

// 全局捕获异常,防止进程崩溃
runAllDemo().catch((err) => {
  console.error('脚本运行异常:', err);
});

请添加图片描述


完整代码逐段详细解析 file-type 兼容Node稳定版示例

一、头部依赖导入与全局常量

const { fileTypeFromFile, fileTypeFromBuffer } = require('file-type');
const fs = require('fs-extra');
const path = require('path');
const { Readable } = require('stream');

// 全局测试工作目录
const WORKSPACE = path.resolve(__dirname, './fileTypeTestSpace');
// 各类测试素材路径
const TEST_FILES = {
  jpg: path.join(WORKSPACE, 'demo.jpg'),
  png: path.join(WORKSPACE, 'demo.png'),
  zip: path.join(WORKSPACE, 'bundle.zip'),
  pdf: path.join(WORKSPACE, 'doc.pdf'),
  mp4: path.join(WORKSPACE, 'video.mp4'),
  fakeJpg: path.join(WORKSPACE, 'fake.jpg'), // 实际是zip,后缀篡改
  empty: path.join(WORKSPACE, 'empty.txt'),
};
  1. file-type 两个核心API
    • fileTypeFromFile:直接传入本地文件路径识别,日常脚本最简便;
    • fileTypeFromBuffer:传入二进制Buffer识别,适配内存文件、分片读取头部;
      舍弃了fileTypeFromStream,因为新版仅支持浏览器Web流,Node原生Readable会报pipeThrough不存在
  2. fs-extra:增强文件工具,一键创建多层目录、文件,无需手动判断文件夹是否存在;
  3. path:跨平台路径处理,自动适配鸿蒙PC、Windows正反斜杠,统一转为绝对路径避免找不到文件;
  4. 常量 TEST_FILES:预定义一套模拟工程文件,包含一张篡改后缀的恶意文件fake.jpg,用来演示安全校验核心场景。

二、initTestAssets 自动生成全套测试素材

async function initTestAssets() {
  await fs.ensureDir(WORKSPACE);
  // 批量创建空文件占位
  for (const fp of Object.values(TEST_FILES)) {
    await fs.ensureFile(fp);
  }
  // 写入zip魔数二进制头部到fake.jpg,模拟篡改后缀恶意文件
  const zipHeader = Buffer.from([80, 75, 3, 4]);
  await fs.writeFile(TEST_FILES.fakeJpg, zipHeader);
  console.log('✅ 测试素材文件自动创建完成(包含篡改后缀的fake文件)\n');
}
  1. fs.ensureDir:自动创建根测试文件夹;
  2. fs.ensureFile:循环创建所有测试文件,不存在的父目录自动生成,不用手动建文件夹;
  3. 核心演示逻辑80 75 3 4 是zip文件固定头部魔数,写入后缀为.jpg的文件,模拟黑客篡改后缀绕过前端校验,用来证明file-type靠二进制识别,不受文件名欺骗。

三、printTypeResult 统一打印工具函数

function printTypeResult(filePath, result) {
  const fileName = path.basename(filePath);
  console.log(`文件:${fileName}`);
  if (!result) {
    console.log('  → 无法识别该文件类型\n');
    return;
  }
  console.log(`  MIME:${result.mime}`);
  console.log(`  后缀:.${result.ext}\n`);
}

封装重复打印逻辑,统一输出文件名、识别出的MIME类型、真实文件后缀;

  • result.mime:标准媒体类型,如image/pngapplication/zip,后端设置HTTP响应头必备;
  • result.ext:真实文件后缀,不带.,用来重命名、分类文件。

四、getFileHeadBuffer 超大文件低内存兼容工具(重点修复方案)

async function getFileHeadBuffer(filePath, readSize = 4100) {
  const fd = await fs.open(filePath, 'r');
  const buf = Buffer.alloc(readSize);
  await fs.read(fd, buf, 0, readSize, 0);
  await fs.close(fd);
  return buf;
}

作用

替代失效的fileTypeFromStream,Node环境处理GB级大文件的低内存方案:

  1. 仅打开文件读取前4100字节,绝大多数文件格式的魔数都存在文件最开头,不需要读取整个文件;
  2. 使用底层文件句柄fd分片读取,不会把完整大文件加载进内存,避免内存溢出;
  3. 读取完成立刻关闭文件句柄,防止文件占用。

五、示例1 demoDetectByFilePath 路径直接识别(最常用)

async function demoDetectByFilePath() {
  console.log('===== 示例1:通过本地文件路径识别真实文件类型 =====');
  for (const filePath of Object.values(TEST_FILES)) {
    const type = await fileTypeFromFile(filePath);
    printTypeResult(filePath, type);
  }
}

业务场景:本地脚本、文件管理工具、构建脚本,直接传文件路径一键识别,内部自动读取文件头部字节,开箱即用。

六、示例2 demoDetectByBuffer 完整二进制Buffer识别

async function demoDetectByBuffer() {
  console.log('===== 示例2:读取文件完整Buffer,二进制识别类型 =====');
  const target = TEST_FILES.fakeJpg;
  const buf = await fs.readFile(target);
  const type = await fileTypeFromBuffer(buf);
  printTypeResult(target, type);
  console.log('结论:虽然后缀是jpg,但二进制识别为压缩包zip,可拦截恶意上传\n');
}

核心安全场景:后端文件上传

前端上传文件到服务器内存Buffer,不落地磁盘先检测真实格式,识别出篡改后缀的恶意zip直接拦截,避免写入服务器磁盘造成漏洞。

七、示例3 demoLargeFileHeadDetect 超大文件分片识别

async function demoLargeFileHeadDetect() {
  console.log('===== 示例3:超大文件低内存识别,仅读取文件头部4100字节 =====');
  const target = TEST_FILES.mp4;
  // 只读取头部,不加载整个大文件
  const headBuf = await getFileHeadBuffer(target, 4100);
  const type = await fileTypeFromBuffer(headBuf);
  printTypeResult(target, type);
  console.log('优势:GB级视频/镜像仅读取头部,无内存溢出风险\n');
}

适合视频、镜像、压缩包等巨型文件,不用完整读取,仅靠头部魔数即可识别,内存占用极低。

八、示例4 demoCustomMemoryBuffer 纯内存二进制识别

async function demoCustomMemoryBuffer() {
  console.log('===== 示例4:纯内存二进制Buffer检测,无需本地磁盘文件 =====');
  // 构造zip魔数内存二进制
  const zipBuf = Buffer.from([80, 75, 3, 4, 0, 0, 0, 0]);
  const type = await fileTypeFromBuffer(zipBuf);
  console.log('内存二进制识别结果:');
  console.log(`  MIME:${type.mime} | 后缀:.${type.ext}\n`);
}

全程不操作本地磁盘,仅内存二进制数据识别,适合接口内存缓存文件、动态生成二进制数据校验。

九、示例5 demoScanWholeDir 批量扫描整个目录识别

async function demoScanWholeDir() {
  console.log('===== 示例5:批量扫描整个目录,批量识别所有文件 =====');
  const entries = await fs.readdir(WORKSPACE, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.isDirectory()) continue;
    const fullPath = path.join(WORKSPACE, entry.name);
    const type = await fileTypeFromFile(fullPath);
    printTypeResult(fullPath, type);
  }
}

自动化脚本场景:遍历素材目录、备份目录,批量识别所有文件真实格式,用于自动整理、分类、清理资源。

十、示例6 demoImageUploadCheck 后端上传安全校验(生产核心)

async function demoImageUploadCheck() {
  console.log('===== 示例6:后端上传校验:仅允许真实图片文件 =====');
  const uploadFiles = [TEST_FILES.png, TEST_FILES.fakeJpg, TEST_FILES.mp4];
  for (const fp of uploadFiles) {
    const type = await fileTypeFromFile(fp);
    const fileName = path.basename(fp);
    const isImage = type?.mime?.startsWith('image/');
    if (isImage) {
      console.log(`${fileName} 合法图片,允许上传`);
    } else {
      console.log(`${fileName} 不是真实图片,拦截上传`);
    }
  }
  console.log('');
}

后端接口标准安全逻辑:判断识别出的MIME是否以image/开头,拦截改后缀的压缩包、视频、可执行文件,防止上传漏洞。

十一、示例7 demoFileClassify 文件自动分类

async function demoFileClassify() {
  console.log('===== 示例7:按文件大类自动分类(压缩/文档/媒体) =====');
  const allFilePaths = Object.values(TEST_FILES);
  const group = {
    archive: [],
    document: [],
    media: [],
    unknown: [],
  };
  for (const fp of allFilePaths) {
    const type = await fileTypeFromFile(fp);
    const name = path.basename(fp);
    if (!type) {
      group.unknown.push(name);
      continue;
    }
    const mime = type.mime;
    if (['application/zip', 'application/gzip', 'application/x-7z-compressed'].includes(mime)) {
      group.archive.push(name);
    } else if (mime.startsWith('application/pdf') || mime.includes('officedocument')) {
      group.document.push(name);
    } else if (mime.startsWith('image/') || mime.startsWith('video/') || mime.startsWith('audio/')) {
      group.media.push(name);
    } else {
      group.unknown.push(name);
    }
  }
  console.log('文件分类结果:');
  console.log('压缩包:', group.archive);
  console.log('文档:', group.document);
  console.log('媒体文件:', group.media);
  console.log('无法识别:', group.unknown, '\n');
}

素材管理后台、自动化整理脚本:根据MIME大类把文件分为压缩包、文档、音视频图片、无法识别四类,自动归类展示。

十二、统一执行入口 runAllDemo

async function runAllDemo() {
  // 1. 生成测试素材
  await initTestAssets();

  // 2. 依次执行所有兼容Node的示例
  await demoDetectByFilePath();
  await demoDetectByBuffer();
  await demoLargeFileHeadDetect();
  await demoCustomMemoryBuffer();
  await demoScanWholeDir();
  await demoImageUploadCheck();
  await demoFileClassify();

  console.log('🎉 file-type 全部功能示例执行完毕');
}

// 全局捕获异常,防止进程崩溃
runAllDemo().catch((err) => {
  console.error('脚本运行异常:', err);
});
  1. 执行顺序约束:必须先执行initTestAssets生成测试文件,后面所有识别示例才有文件可操作;
  2. 顶层catch捕获全部同步、异步报错,打印完整错误堆栈,避免Node进程直接闪退。

整体核心优势与鸿蒙PC适配说明

  1. 纯JS无二进制:file-type不包含任何.node原生模块,鸿蒙ARM64 PC无需ohos-signpost签名,直接node运行无权限报错;
  2. 安全核心能力:不靠文件名后缀,读取二进制魔数识别,杜绝篡改后缀绕过校验的上传漏洞;
  3. 内存友好:提供分片读取头部方案,处理超大视频、镜像无内存溢出;
  4. 全场景覆盖:本地脚本、后端上传接口、自动化批量分类、内存二进制校验全部兼容;
  5. Node全版本稳定:移除浏览器专属WebStream API,仅使用fileTypeFromFile/fileTypeFromBuffer,无版本兼容报错。

安装运行命令

npm i file-type fs-extra
node index.js

TS 精简版 fileTypeDemo.ts

import { fileTypeFromFile } from 'file-type';
import path from 'path';

async function tsTest() {
  const testFile = path.resolve(__dirname, './fileTypeTestSpace/demo.png');
  const res = await fileTypeFromFile(testFile);
  console.log('TS识别结果:', res?.mime, '.' + res?.ext);
}
tsTest().catch(e => console.error(e));

运行安装&执行命令

# 安装依赖
npm i file-type fs-extra
# 执行脚本
node fileTypeDemo.js

代码逐段功能详细讲解

1. 头部导入

const { fileTypeFromFile, fileTypeFromBuffer, fileTypeFromStream } = require('file-type');
const fs = require('fs-extra');
const path = require('path');
const { Readable } = require('stream');
  • fileTypeFromFile:直接传入文件路径检测(日常最方便);
  • fileTypeFromBuffer:传入二进制Buffer检测;
  • fileTypeFromStream:传入文件流检测,适合超大文件;
  • Readable:构造内存二进制流,无需生成本地文件。

2. initTestAssets 自动生成测试文件

自动创建测试目录,重点生成 fake.jpg:后缀是jpg,但二进制头部是zip魔数,用来演示篡改后缀无法欺骗file-type,这是后端上传防攻击核心场景。

3. printTypeResult 统一打印工具

封装重复打印逻辑,统一输出文件名、MIME、后缀,简化各示例代码。

4. 示例1 demoDetectByFilePath 文件路径检测

最常用API,直接传本地文件绝对路径,内部自动读取头部字节,业务脚本、文件管理工具首选。

5. 示例2 demoDetectByBuffer 二进制Buffer检测

后端上传场景常用:前端上传文件到内存Buffer,不落地磁盘直接检测,拦截恶意文件再写入服务器。重点演示 fake.jpg 后缀欺骗失效。

6. 示例3 demoDetectByStream 文件流检测

大文件场景优化,createReadStream创建文件流,file-type读到头部魔数就自动终止流读取,不会加载整个大文件到内存。

7. 示例4 demoCustomMemoryStream 纯内存流检测

完全不操作本地磁盘,内存二进制数据封装为Readable流检测,适合动态生成二进制、内存缓存文件校验。

8. 示例5 demoScanWholeDir 批量遍历目录识别

自动化脚本场景:遍历资源目录批量分类图片、压缩包、文档,批量整理素材。

9. 示例6 demoImageUploadCheck 上传安全校验(核心业务场景)

模拟后端接口逻辑:仅放行真实image/* MIME文件,拦截改后缀的zip/mp4恶意文件,解决上传安全漏洞。

10. 示例7 demoFileClassify 文件自动分组

根据MIME大类把文件分为压缩包、文档、媒体、未知,用于素材后台自动归类展示。

11. 示例8 demoLargeFileStreamDetect 超大文件分片优化

设置小缓冲区highWaterMark,验证流只会读取文件头部少量字节,处理GB级视频/镜像文件无内存溢出风险。

鸿蒙PC Node环境适配说明

  1. file-type 纯JS实现,无任何 .node 原生二进制模块;
  2. 不需要 ohos-signpost 签名,直接node运行不会出现permission denied;
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐