文件上传是现代Web应用程序中常见的功能之一。在这篇博客中,我们将探讨一个简单但完整的前端文件上传实践,同时提供一个后端示例,演示如何处理上传的文件。我们将使用JavaScript作为前端语言,并结合Node.js作为后端环境。让我们开始吧!
前端文件上传
我们首先关注前端部分。我们将使用HTML、CSS和JavaScript来创建一个简单的文件上传表单,然后通过AJAX将文件传递给后端服务器。这里我们假设你已经具备了基本的前端开发知识。
1. HTML结构
首先,我们创建一个HTML结构,包含一个文件上传输入框、上传按钮和用于显示上传进度和信息的元素:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传示例</title>
</head>
<body>
<input type="file" id="videoUploader" accept="video/mp4, video/ogg">
<button id="uploadBtn">上传</button>
<p id="uploadInfo"></p>
<progress id="uploadProgress" value="0" max="100"></progress>
</body>
</html>
2. CSS样式(可选)
你可以根据需要添加一些CSS样式来美化页面。
3. JavaScript逻辑
接下来,我们将使用JavaScript来实现文件上传逻辑。我们从一个配置文件开始,然后实现上传函数和相关的辅助函数:
// config.js
// 导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL
// 这些常量将在前端和后端代码中使用
const BASE_URL = 'http://localhost:8000/';
export const UPLOAD_INFO = {
'NO_FILE': '请先选择文件',
'INVALID_TYPE': '不支持该类型文件上传',
'UPLOAD_FAILED': '上传失败',
'UPLOAD_SUCCESS': '上传成功'
}
export const ALLOWED_TYPE = {
'video/mp4': 'mp4',
'video/ogg': 'ogg'
}
export const CHUNK_SIZE = 64 * 1024;
export const API = {
UPLOAD_VIDEO: BASE_URL + 'upload_video'
}
// index.js
// 在闭包内部,定义一个立即执行的匿名函数,接受document作为参数
((doc) => {
// 获取页面上的一些元素
const oProgress = doc.querySelector('#uploadProgress'); // 上传进度条
const oUploader = doc.querySelector('#videoUploader'); // 文件上传输入框
const oInfo = doc.querySelector('#uploadInfo'); // 用于显示上传信息
const oBtn = doc.querySelector('#uploadBtn'); // 上传按钮
let uploadedSize = 0; // 已上传的文件大小,用于控制分块上传
// 初始化函数
const init = () => {
bindEvent(); // 绑定事件
}
// 绑定事件函数
function bindEvent() {
oBtn.addEventListener('click', uploadVideo, false); // 点击上传按钮触发上传函数
}
// 异步上传函数
async function uploadVideo() {
// 获取文件对象
const { files: [ file ] } = oUploader;
if (!file) {
// 检查是否选择了文件,如果没有则显示提示信息
oInfo.innerText = UPLOAD_INFO['NO_FILE'];
return;
}
if (!ALLOWED_TYPE[file.type]) {
// 检查文件类型是否被允许,如果不允许则显示提示信息
oInfo.innerText = UPLOAD_INFO['INVALID_TYPE'];
return;
}
// 获取文件的一些基本信息
const { name, type, size } = file;
const fileName = new Date().getTime() + '_' + name;
let uploadedResult = null;
oProgress.max = size;
oInfo.innerText = '';
while (uploadedSize < size) {
// 使用slice方法将文件切割成小块,实现分块上传
const fileChunk = file.slice(uploadedSize, uploadedSize + CHUNK_SIZE);
// 创建FormData对象,用于传递文件块和相关信息
const formData = createFormData({
name,
type,
size,
fileName,
uploadedSize,
file: fileChunk
});
try {
// 使用axios库发送POST请求,将文件块上传到后端
uploadedResult = await axios.post(API.UPLOAD_VIDEO, formData);
} catch (e) {
// 捕获异常,如果上传失败,则显示提示信息
oInfo.innerText = `${ UPLOAD_INFO['UPLOAD_FAILED'] }(${ e.message })`;
return;
}
// 更新已上传的文件大小和进度条
uploadedSize += fileChunk.size;
oProgress.value = uploadedSize;
}
// 上传成功后,显示上传成功的提示信息,并将文件输入框置空
oInfo.innerText = UPLOAD_INFO['UPLOAD_SUCCESS'];
oUploader.value = null;
// 根据后端返回的视频URL,创建一个视频元素并展示在页面上
createVideo(uploadedResult.data.video_url);
}
// 创建FormData函数,将文件块和相关信息添加到FormData对象中
function createFormData ({
name,
type,
size,
fileName,
uploadedSize,
file
}) {
const fd = new FormData();
fd.append('name', name);
fd.append('type', type);
fd.append('size', size);
fd.append('fileName', fileName);
fd.append('uploadedSize', uploadedSize);
fd.append('file', file);
return fd;
}
// 创建视频元素函数,用于在页面上展示上传成功的视频
function createVideo(src) {
const oVideo = document.createElement('video');
oVideo.controls = true;
oVideo.width = '500';
oVideo.src = src;
document.body.appendChild(oVideo);
}
init(); // 初始化,执行绑定事件函数
})(document);
-
引入依赖项:
config.js
:导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL。axios
:一个用于发起HTTP请求的库,用于将文件块上传到后端。
-
创建一个立即执行的匿名函数:
- 接受
document
作为参数,并使用doc
来代表document
对象。 - 这种方式可以避免全局变量污染,确保代码运行在独立的作用域中。
- 接受
-
获取页面上的元素:
oProgress
:代表上传进度条的<progress>
元素。oUploader
:代表文件上传输入框的<input type="file">
元素。oInfo
:用于显示上传信息的<p>
元素。oBtn
:代表上传按钮的<button>
元素。
-
初始化函数:
- 调用
bindEvent()
函数,用于绑定点击上传按钮时的事件处理函数。
- 调用
-
绑定事件函数:
- 使用
addEventListener()
方法,为上传按钮添加一个点击事件监听器。 - 当点击上传按钮时,将触发
uploadVideo()
函数进行文件上传。
- 使用
-
异步上传函数
uploadVideo()
:- 获取文件对象
file
,这是通过文件上传输入框oUploader
获取的。 - 检查是否选择了文件,如果没有则显示提示信息,并返回。
- 检查文件类型是否被允许上传,如果不允许则显示提示信息,并返回。
- 获取文件的一些基本信息,如文件名、文件类型和文件大小等。
- 使用
while
循环,对文件进行分块上传:- 使用
file.slice()
方法将文件切割成小块(块大小由常量CHUNK_SIZE
定义)。 - 创建
FormData
对象,将文件块和相关信息添加到其中,用于传递给后端。 - 使用
axios.post()
方法,将文件块上传到后端指定的URL(由常量API.UPLOAD_VIDEO
定义)。 - 如果上传失败,捕获异常并显示提示信息,并返回。
- 更新已上传的文件大小和进度条的值。
- 使用
- 上传完成后,显示上传成功的提示信息,并将文件输入框的值置空。
- 根据后端返回的视频URL,调用
createVideo()
函数在页面上创建视频元素并展示上传成功的视频。
- 获取文件对象
-
创建
FormData
函数createFormData()
:- 将文件块和相关信息添加到
FormData
对象中,用于传递给后端。
- 将文件块和相关信息添加到
-
创建视频元素函数
createVideo()
:- 用于在页面上创建视频元素,并设置视频URL为上传成功后后端返回的视频URL。
-
初始化函数
init()
:- 执行
bindEvent()
函数,将上传按钮和事件处理函数绑定在一起。
- 执行
-
最后,通过调用
init()
函数来启动整个前端代码。
我们在前端代码中引入了axios
库,这是一个用于发起HTTP请求的常用工具。在实际项目中,你需要确保在HTML文件中引入axios
库,或者使用其他类似功能的库。
以上代码中,我们首先定义了一些常量,如允许上传的文件类型、块大小等。然后我们通过事件监听捕获“上传”按钮的点击事件,执行上传函数。在上传函数中,我们通过分块上传的方式,将文件切割为多个小块,并逐个发送给后端进行处理。上传过程中,我们还会实时更新上传进度条和显示上传信息。
后端文件处理
现在让我们关注后端处理部分。我们将使用Node.js和Express来搭建一个简单的后端服务器,接收前端传递的文件块,并将它们合并成完整的文件。
1. 安装依赖
首先,在项目目录下创建一个package.json
文件,然后运行以下命令安装依赖:
以上代码使用Express框架搭建了一个简单的服务器,并设置了文件上传的路由。我们可以通过/upload_video
接口来上传文件块,后端根据上传的信息,将文件块保存在服务器端,直到所有块都接收完成后,合并成完整的文件。
npm init -y
npm install express body-parser express-fileupload
2. 后端代码
接下来,我们创建一个server.js
文件,编写后端代码:
const express = require('express');
const bodyParser = require('body-parser');
const uploader = require('express-fileupload');
const { extname, resolve } = require('path');
const { existsSync, appendFileSync, writeFileSync } = require('fs');
const app = express();
const PORT = 8000;
// 设置中间件,解析请求的JSON数据和文件上传
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(uploader());
// 设置静态资源目录,用于存放上传的临时文件块
app.use('/', express.static('upload_temp'));
// 允许跨域访问
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-origin', '*');
res.header('Access-Control-Allow-Methods', 'POST,GET');
next();
});
// 处理文件上传的POST请求,对应的URL为/upload_video
app.post('/upload_video', (req, res) => {
// 从请求体中获取传递的文件信息和文件块
const { name, type, size, fileName, uploadedSize } = req.body;
const { file } = req.files;
if (!file) {
// 检查是否传递了文件块,如果没有则返回错误信息
res.send({
code: 1001,
msg: 'No file uploaded'
});
return;
}
if (!ALLOWED_TYPE[type]) {
// 检查文件类型是否被允许,如果不允许则返回错误信息
res.send({
code: 1002,
msg: 'The type is not allowed for uploading.'
});
return;
}
const filename = fileName + extname(name);
const filePath = resolve(__dirname, './upload_temp/' + filename);
if (uploadedSize !== '0') {
if (!existsSync(filePath)) {
// 检查文件是否存在,如果不存在则返回错误信息
res.send({
code: 1003,
msg: 'No file exists'
});
return;
}
// 文件已存在,则将当前文件块追加到已有的文件中
appendFileSync(filePath, file.data);
// 返回成功信息和视频URL
res.send({
code: 0,
msg: 'Appended',
video_url: 'http://localhost:8000/' + filename
});
return;
}
// 第一个文件块,创建一个新文件并写入当前块的数据
writeFileSync(filePath, file.data);
// 返回成功信息
res.send({
code: 0,
msg: 'File is created'
});
});
// 启动服务,监听指定端口
app.listen(PORT, () => {
console.log('Server is running on ' + PORT);
});
-
引入依赖项:
express
:Express框架,用于构建Web服务器。body-parser
:解析请求体中的JSON数据。express-fileupload
:处理文件上传请求。path
:Node.js内置模块,用于处理文件路径。fs
:Node.js内置模块,用于文件系统操作。
-
创建Express应用:
const app = express(); const PORT = 8000;
-
使用中间件:
bodyParser
中间件用于解析请求体中的JSON数据和表单数据。uploader
中间件用于处理文件上传请求。- 设置静态资源目录
'/upload_temp'
,用于存放上传的临时文件块。
-
设置跨域访问: 使用
app.all()
方法设置允许跨域请求的响应头。 -
处理文件上传请求:
- 使用
app.post('/upload_video', ...)
定义处理文件上传的POST请求,对应的URL是/upload_video
。 - 从请求体中获取传递的文件信息和文件块,包括文件名、文件类型、文件大小、文件块的上传起始位置等。
- 检查文件块是否存在,如果不存在则返回错误信息。
- 检查文件类型是否被允许,如果不允许则返回错误信息。
- 根据文件名和文件类型生成新的文件名,并计算文件的存储路径。
- 如果不是第一个文件块,将当前文件块追加到已有的文件中。
- 如果是第一个文件块,创建一个新文件并写入当前块的数据。
- 返回成功信息,并包含上传成功后的视频URL。
- 使用
-
启动服务: 使用
app.listen()
方法启动服务器,监听指定的端口(在此例中为8000)。
运行项目
现在,我们已经完成了前端和后端的代码编写。接下来,我们需要在本地运行项目,以测试文件上传的功能。
1. 创建上传临时目录
在项目根目录下创建一个名为upload_temp
的文件夹,用于保存上传的临时文件块。
2. 启动服务器
运行以下命令启动后端服务器:
node server.js
3. 启动前端
将前端代码(HTML、CSS和JavaScript)放置在一个文件夹中,然后在该文件夹下运行一个HTTP服务器(使用npm和yarn也没问题, npm install——npm run dev/yarn——yarn dev),比如使用http-server
:
npx http-server
现在,通过浏览器访问前端页面(通常是http://localhost:8080
)。选择一个支持的视频文件(.mp4或.ogv格式),点击上传按钮,你将看到上传进度条显示上传进度。上传完成后,页面将显示上传成功的信息,并在页面上展示上传的视频文件。
结束语
在本篇博客中,我们学习了如何实现一个简单的前端文件上传功能,并结合Node.js和Express搭建了一个后端服务器来处理文件上传。在实际项目中,你可以根据需求对文件上传的功能进行扩展和优化。希望这篇博客能够帮助你理解文件上传的基本原理和实践方法。谢谢阅读!
如果你对前端文件上传还有疑问,或者对其他技术主题感兴趣,欢迎加入讨论群或者关注!