常规方案

使用FileReader以utf-8格式读取文件,根据文件内容是否包含乱码字符,来判断文件是否为utf-8。

如果存在,即文件编码非utf-8,反之为utf-8。

代码如下:

const isUtf8 = async (file: File) => {
  return await new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsText(file);

    reader.onloadend = (e: any): void => {
      const content = e.target.result;
      const encodingRight = content.indexOf("") === -1;

      if (encodingRight) {
        resolve(encodingRight);
      } else {
        reject(new Error("编码格式错误,请上传 UTF-8 格式文件"));
      }
    };

    reader.onerror = () => {
      reject(new Error("文件内容读取失败,请检查文件是否损坏"));
    };
  });
};

该方法问题在于,如果文件非常大,比如几个G,浏览器读到的内容直接放在内存中,fileReader实例会直接触发onerror,抛出错误,有时浏览器会直接崩溃。

大文件方案

对于大文件,可以对文件内容进行抽样,对文件进行切片,这里使用100片。对切出的每片文件再切取前面1kb大小的片段,以string方式读取。如果1024B可能正好切在某个汉字编码的中间,导致以string方式读取时出错,即首尾可能出现,被认为是非utf-8片段。这时可以取1kb对应字符串的前半段,再去判断是否存在。

上述常数可以根据需求进行调整。

代码如下:

const getSamples = (file: File) => {
  const filesize = file.size;
  const parts: Blob[] = [];
  if (filesize < 50 * 1024 * 1024) {
    parts.push(file);
  } else {
    let total = 100;
    const sampleSize = 1024 * 1024;
    const chunkSize = Math.floor(filesize / total);
    let start = 0;
    let end = sampleSize;
    while (total > 1) {
      parts.push(file.slice(start, end));
      start += chunkSize;
      end += chunkSize;
      total--;
    }
  }
  return parts;
};

const isUtf8 = (filePart: Blob) => {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();

    fileReader.readAsText(filePart);

    fileReader.onload = (e) => {
      const str = e.target?.result as string;
      // 大致取一半
      const sampleStr = str?.slice(4, 4 + str?.length / 2);
      if (sampleStr.indexOf("�") === -1) {
        resolve(void 0);
      } else {
        reject(new Error(编码格式错误,请上传 UTF-8 格式文件"));
      }
    };

    fileReader.onerror = () => {
      reject(new Error(文件内容读取失败,请检查文件是否损坏"));
    };
  });
};

export default async function (file: File) {
  const samples = getSamples(file);
  let res = true;

  for (const filePart of samples) {
    try {
      await isUtf8(filePart);
    } catch (error) {
      res = false;
      break;
    }
  }
  return res;
}
06-03 06:44