JavaScript高级 - nodejs+koa2实现文件上传大文件切片上传断点续传(服务器端)


前言

最近由于项目需要,研究了一下JS中的文件上传,并做了一下简单的总结和梳理。下面将对该功能分两部分(服务端和客户端)进行分别介绍。本文将只介绍服务端部分。

一、环境准备及第三方库

  • 我们将采用nodejs+koa2来实现文件上传的服务端部分
  • nodejs版本:v12.16.1
  • koa版本:v2.13.1
  • formidable:v1.2.2 用于解析post请求中传递的参数,Content-Type为:application/x-www-form-urlencoded
  • koa-router:v10.0.0 用于配置路由
  • koa-static: v5.0.0 读取静态文件中间件
  • koa2-cors: v2.0.6 用于cors跨域设置
  • multiparty:v4.2.2 用于解析和上传form-data文件
  • spark-md5:v3.0.1 用于根据文件内容生成字符串(相同内容的文件生成的值是一样的)
  • nodemon:v2.0.7 用该命令运行js文件可自动重启服务

二、项目结构

server:根目录

  • node_modules:自动生成,存放所有依赖库
  • upload:用于存放上传后的文件
  • server.js:api接口文件,定义所有的上传接口
  • package.json:项目的配置文件

三、API接口说明(server.js)

1.基本配置
  • 首先我们先将所有要用到的库在文件的头部引入
  • 配置一些全局属性,如服务器地址、端口号、文件保存路径等等
  • 因为是前后端分离,所有可能会涉及到跨域请求,需要配置cors跨域
  • 最后再应用一下koastatic中间件用于访问静态资源文件

整体骨架代码如下:

const Koa = require('koa');
const Router = require('koa-router');
const koastatic = require('koa-static');
const fs = require('fs');
const formidable = require('formidable');
const multiparty = require('multiparty');
const SparkMD5 = require('spark-md5');
const path = require('path');
const app = new Koa();
let router = new Router();

//中间件:设置允许跨域
app.use(async (ctx, next) => {
   
    ctx.set('Access-Control-Allow-Origin', '*');
    //处理OPTIONS请求
    ctx.request.methods === 'OPTIONS' ? ctx.body = '请求测试成功' : await next();
});

const host = '127.0.0.1',
    port = 3000;
const HOSTNAME = `${
     host}:${
     port}`;
const SERVER_PATH = `${
     __dirname}/upload`;
app.listen(port, function () {
   
    console.log('======================================================================');
    console.log(`The server started at port: ${
     port}, you can access it by ${
     HOSTNAME}`);
    console.log('======================================================================');
});

//.......
//这里添加文件上传的API接口
//.......

app.use(koastatic('./'));
app.use(router.routes());
app.use(router.allowedMethods());
2. 公共函数封装

在实现各个接口前难免会涉及到一些重复的代码,这个时候我们就需要进行一些必要的封装,以实现代码的复用。
接下来我们就一起来分析并封装一下常用的方法

  • 检测文件是否存在
    • 本案例中我们采用nodejs内置的fs模块的access函数来检测文件是否已经存在
    • 利用Promise进行管理
    • 关键代码:fs.access(filepath, fs.constants.F_OK, err=>{…})
    • 另:fs模块中有个exists方法也可以用于检测文件是否存在,但官方文档中已经弃用,并推荐使用access方法。
 //检测文件是否已经存在
const exists = function exists(path){
   
	return new Promise((resolve, reject)=>{
   
		fs.access(path, fs.constants.F_OK, err=>{
   
			if(err){
   
				resolve(false);
				return;
			}
			resolve(true);
		});
	});
}
  • 利用multiparty解析请求中的文件信息
    • 利用该插件可实现form-data格式的请求中的文件解析和提取
    • 该模块配置项中有个uploadDir属性,如果指定则会自动上传文件
    • 因为本文还将涉及到自定义文件名上传文件,所以这里我们将该方法封装为可配置的
    • 关键代码:new multiparty.From(config).parse(req,(err, fields, files)=>{})
//利用multiparty插件解析前端传来的form-data格式的数据,并上传至服务器
const multipartyUpload = function multipartyUpload(req, autoUpload){
   
	let config = {
   
		maxFieldsSize : 200 * 1024 *1024
	}
	if(autoUpload) config.uploadDir = SERVER_PATH;
	return new Promise((resolve,reject)=>{
   
		new multiparty.Form(config).parse(req, (err, fields, files)=>{
   
			if(err){
   
				reject(err);
				return;
			}
			resolve({
   
			fields,
			files
			});
		});
	});
}
  • 将文件内容写入服务器
    • 该方法中我们将以两种方式进行文件的写入
    • 一种是:对于前端传过来的form-data格式的文件我们将以流的方式进行写入
    • 另一种:则是将内容直接写入到文件,比如BASE64格式的图片文件
    • 这里我们将用到内置模块fs中的createReadStream和createWriteStream用于以流的方式写入,还有fs中的writeFile用于将内容直接写入
    • 关键代码: fs.createReadStream(filepath); fs.createWriteStream(serverpath); readStream.pipe(writeStream); fs.writeFile(serverpath,file,err=>{…})
//将传进来的文件数据写入服务器
//form-data格式的数据将以流的形式写入
//BASE64格式数据则直接将内容写入
const writeFile = function writeFile(serverPath, file, isStream){
   
	return new Promise((resolve, reject)=>{
   
		if(isStream){
   
			try{
   
				let readStream = fs.createReadStream(file.path);
				let writeStream = fs.createWriteStream(serverPath);
				readStream.pipe(writeStream);
				readStream.on('end',()=>{
   
					resolve({
   
						result: true,
						message: '上传成功!'
					});
					fs.unlinkSync(file.path);
				});
			}catch(err){
   
				resolve({
   
					result: false,
					message: err
				})
			}
		}else{
   
			fs.writeFile(serverPath,file, err=>{
   
				if(err){
   
					resolve({
   
						result: false,
						message: err
					})
					return;
				}
				resolve({
   
					result: true,
					message: '上传成功!'
				});
			});
		}
	});
}
  • post请求中application/json或application/x-www-form-urlencoded格式的参数解析
    • 对于from-data形式的文件信息我们可以用multiparty插件进行解析和提取,但是对于其它格式的一些参数,multiparty就无法解析到了
    • 这里我们利用formidable进行解析post参数
    • formidable可以将a=xxx&b=zzz格式的参数解析为json格式,前端请求时需用qs进行相应的格式处理
    • 另外:koa-bodyparser也可以用来解析,但是尝试用了一下都没有成功,所以改用formidable
    • 关键代码:new formidable.IncomingForm().parse(req,(err, fields)=>{…})
//解析post请求参数,content-type为application/x-www-form-urlencoded 或 application/josn
const parsePostParams = function parsePostParams(req){
   
	return new Promise((resolve, reject)=>{
   
		let form = new formidable.IncomingForm();
		form.parse(req,(err,fields)=>{
   
			if(err){
   
				reject(err);
				return;
			}
			resolve(fields);
		});
	});
}
  • 大文件切片上传后再合并
    • 在后面要讲的大文件切片上传,断点续传中,一个大文件会被切成n多个小文件上传
    • 在上传后我们需要再将这些切片文件合并成一个文件,从而实现大文件上传
    • 主要利用内置fs模块中的appendFileSync和readFileSync方法
    • 关键代码:fs.appendFileSync(serverFilePath, fs.readFileSync(filePath))
const mergeFiles = mergeFiles(hash, count){
   
	return new Promise((resolve, reject)=>{
   
		const dirPath = `${
     SERVER_PATH}/${
     hash}`;
		if(!fs.existsSync(dirPath)){
   
			reject('还没上传文件,请先上传文件');
			return;
		}
		const filelist = fs.readdirSync(dirPath);
		if(filelist.length < count){
   
			reject('文件还未上传完成,请稍后再试');
			return;
		}
		let suffix;
		filelist
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值