要实现上传文件内容的校验,包含判断逻辑和上传限制。
判断文件是否为二进制的思路
- 文件大小校验:确保文件不超过 40MB。
- 读取文件内容:读取文件的前几 KB(例如 4KB)以优化性能。
- 尝试解码:使用
TextDecoder尝试将文件内容解码为 UTF-8 文本。 - 检查字符合法性:
- 合法字符包括:可打印字符(字母、数字、标点符号、空格等)、回车(
\r)、换行(\n)、制表符(\t)等。 - 不可读字符:除上述外的控制字符(如
U+0000到U+001F中除\r、\n、\t外的字符,以及U+007F)。 - 如果不可读字符比例过高(例如 >10%),认为文件是二进制。
- 合法字符包括:可打印字符(字母、数字、标点符号、空格等)、回车(
- 上传限制:仅允许非二进制(可读文本)文件上传,并提供用户提示。
完整代码
以下代码实现了文件类型判断、40MB 大小限制,并禁止二进制文件上传,同时在界面上显示提示信息。
// 最大文件大小:40MB (40 * 1024 * 1024 字节)
const MAX_FILE_SIZE = 40 * 1024 * 1024;
async function isReadableText(file) {
// 检查文件大小
if (file.size > MAX_FILE_SIZE) {
return { isValid: false, message: `文件大小 ${Math.round(file.size / 1024 / 1024)}MB,超过最大限制 40MB` };
}
try {
// 读取文件前 4KB(优化性能)
const buffer = await file.slice(0, 4096).arrayBuffer();
// 使用 TextDecoder 解码为 UTF-8 文本
const decoder = new TextDecoder('utf-8', { fatal: false });
const text = decoder.decode(buffer);
// 检查不可打印字符的比例
let unreadableCount = 0;
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
// 仅将除 \r (0x0D)、\n (0x0A)、\t (0x09) 外的控制字符 (0x00-0x1F) 和 0x7F 视为不可读
if ((code < 0x20 && code !== 0x0D && code !== 0x0A && code !== 0x09) || code === 0x7F) {
unreadableCount++;
}
}
// 如果不可读字符比例超过阈值(10%),认为是二进制文件
const unreadableRatio = unreadableCount / text.length;
const threshold = 0.1; // 可根据需求调整
if (unreadableRatio >= threshold) {
return { isValid: false, message: '文件包含过多不可读字符,可能是二进制文件' };
}
return { isValid: true, message: '文件是可读文本' };
} catch (e) {
// 解码失败,认为是二进制文件
return { isValid: false, message: '无法解码文件,可能是二进制文件' };
}
}
// 处理文件上传
document.getElementById('fileInput').addEventListener('change', async (event) => {
const file = event.target.files[0];
const messageElement = document.getElementById('message');
if (!file) {
messageElement.textContent = '请选择一个文件';
return;
}
// 校验文件
const result = await isReadableText(file);
messageElement.textContent = result.message;
if (result.isValid) {
// 允许上传(此处模拟上传逻辑)
messageElement.style.color = 'green';
console.log('可以上传文件:', file.name);
// 实际上传逻辑,例如使用 FormData 发送到服务器
// const formData = new FormData();
// formData.append('file', file);
// fetch('/upload', { method: 'POST', body: formData });
} else {
// 禁止上传
messageElement.style.color = 'red';
console.log('禁止上传:', result.message);
// 清空输入框,防止用户提交
event.target.value = '';
}
});
HTML 示例
<!DOCTYPE html>
<html>
<head>
<title>文件上传校验</title>
<style>
#message { margin-top: 10px; font-size: 16px; }
</style>
</head>
<body>
<input type="file" id="fileInput" />
<div id="message"></div>
<script src="script.js"></script>
</body>
</html>
关键点说明
-
二进制文件判断:
- 使用
TextDecoder解码文件内容,若解码失败(例如遇到无效 UTF-8 序列),直接判定为二进制。 - 检查不可读字符比例,排除合法字符(
\r、\n、\t、空格、标点符号等)。如果不可读字符比例超过 10%,认为是二进制文件。 - 不可读字符定义为:控制字符(
U+0000到U+001F中除\r、\n、\t外的字符)以及U+007F。
- 使用
-
文件大小限制:
- 限制文件大小不超过 40MB(
40 * 1024 * 1024字节)。 - 超大文件直接返回错误提示,并禁止上传。
- 限制文件大小不超过 40MB(
-
性能优化:
- 只读取文件前 4KB(
file.slice(0, 4096)),足以判断文件是否为二进制,且对大文件友好。
- 只读取文件前 4KB(
-
用户体验:
- 通过
<div id="message">显示校验结果,成功为绿色,失败为红色。 - 如果文件不合法(二进制或超大),清空
<input>元素(event.target.value = ''),防止用户提交。 - 返回对象
{ isValid, message }提供详细的错误原因,便于用户理解。
- 通过
-
合法字符支持:
- 回车(
\r)、换行(\n)、制表符(\t)、空格以及标点符号(如.,!?;:'")都被视为合法字符,不影响文本可读性判断。 - 标点符号通常位于 ASCII 范围(
0x20及以上)或 Unicode 标点范围,不会触发不可读条件。
- 回车(
上传逻辑
- 当前代码仅模拟上传逻辑(打印到控制台)。实际应用中,可以在
result.isValid为true时,使用FormData和fetch向服务器发送文件,例如:if (result.isValid) { const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/upload', { method: 'POST', body: formData }); console.log('上传成功:', await response.json()); } catch (e) { console.error('上传失败:', e); messageElement.textContent = '上传失败,请重试'; messageElement.style.color = 'red'; } } - 服务器端也应校验文件大小和类型,以防止绕过前端限制。
注意事项
-
编码支持:
- 代码假设文件为 UTF-8 编码。如果需要支持其他编码(如 GBK、UTF-16),可以使用
jschardet检测编码并动态设置TextDecoder参数。 - 示例:
new TextDecoder(detectedEncoding)。
- 代码假设文件为 UTF-8 编码。如果需要支持其他编码(如 GBK、UTF-16),可以使用
-
阈值调整:
- 不可读字符比例阈值(
threshold = 0.1)可能需要根据文件类型调整。例如,某些日志文件可能包含较多制表符或换行符,需放宽阈值。
- 不可读字符比例阈值(
-
文件类型辅助校验:
- 可以通过
file.type(MIME 类型)或文件扩展名(如.txt、.csv)辅助判断。例如,限制只允许text/*类型:if (!file.type.startsWith('text/') && !['.txt', '.csv', '.json'].includes(file.name.slice(file.name.lastIndexOf('.')))) { return { isValid: false, message: '不支持的文件类型,仅允许文本文件' }; } - 但 MIME 类型和扩展名可能被伪造,因此仍需内容校验。
- 可以通过
-
边缘情况:
- 空文件或极小文件:代码会正确处理(
text.length为 0 时,unreadableRatio为 0)。 - 二进制文件伪装为文本:某些二进制文件可能包含少量可读字符,阈值
0.1能有效识别大部分情况。 - 特殊文本文件:如包含大量 Unicode 字符(如中文标点),不会被误判为二进制。
- 空文件或极小文件:代码会正确处理(
-
安全性:
- 前端校验可以被绕过,服务器端必须重复校验文件大小和内容。
- 建议在服务器端使用类似逻辑(读取文件头,检查不可读字符)或专用库(如 Python 的
chardet)。
测试用例
- 合法文本文件:
.txt文件,包含字母、数字、空格、回车、换行、标点符号。 - 带制表符的文件:
.csv文件,包含\t分隔符。 - 二进制文件:
.jpg、.pdf、.exe,应被拒绝。 - 超大文件:超过 40MB 的文件,应被拒绝。
- 空文件:空
.txt文件,应通过校验。 - 伪装文件:二进制文件重命名为
.txt,应被识别为二进制。
总结
通过 TextDecoder 解码和不可读字符比例检查(排除 \r、\n、\t 等合法字符),代码能有效判断文件是否为二进制,并禁止二进制文件上传。40MB 大小限制和前 4KB 读取优化确保了性能。