手把手教你搭建一个【文件共享平台】系列教程第六话——koa后端路由处理文件读写+CURD

本话概要

又见面啦~9102年马上就要结束了,我要抓住时间的尾巴多出几篇博文!!等待候补车票兑现的第n天😭

本期涉及的主要内容包括:koa后端处理文件及文件夹的读写增删改查功能。本期的代码量很大,小伙伴们跟上啊~奥里给

初识文件管理系统

文件共享平台的核心就是文件云管理。需要满足多用户共同上传、下载、删除文件/文件夹。那么从后台的角度,我们需要解决哪些技术问题?

  1. 接收文件流转云服务器的本地文件
  2. 读取云服务器的本地文件转文件流回传
  3. 同时上传多文件问题
  4. 创建、删除文件/文件夹
  5. … …

想象一下平时我们在自己电脑上所能实现的文件操作,现在的区别就是多个用户可以同时对一台电脑的文件做相同的操作。下面我们就进入正题,教大家如何搭建文件处理路由。👇

文件管理路由实现

数据库文件表设计

直接上图。顺便安利一下免费表设计利器dbdiagram.io
在这里插入图片描述
path:文件的路径(根目录->所在文件夹)
name:文件名
type:文件类型(directory or file)
suffix:文件后缀
uploader:上传用户名
uploadtime:上传时间
downloadcount:下载次数
size:文件大小(增加一个)

文件路由

file.js结构

与前几篇教程一样, 我们创建file.js,专门写文件处理的路由文件。如下:

const router = require('koa-router')();
const send = require('koa-send');
const uploadFiles = require('../lib/uploadFiles');
const searchAllFiles = require('../lib/searchAllFiles');
const deleteFile = require('../lib/deleteFile');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

//判断用户是否登录,该中间件需要写在最前面!
router.use(async (ctx,next)=>{
	if (!ctx.session.user) {
		ctx.status = 403;
		ctx.body = {
			msg:'未登录'
		}
	}else{
		await next();
	}	
});

//具体内容...

module.exports = router;

其中,uploadFiles、searchAllFiles、deleteFile是提取出的函数,分别对应上传文件、查询文件、删除文件。

config.js是配置文件,如下:

module.exports = {
	StorageRoot:'./static/Storage',//文件存储路径
	ThumbnailRoot:'./static/Thumbnail',//缩略图存储路径
	url:"http://localhost:8080",//前端页面的url
	ip:'localhost',//后端ip
	port:3000//后端端口
}
单/多文件上传

首先判断单或多文件,用promise.all处理多文件写入本地和存入数据库的功能,代码如下:

// 上传单个或多个文件 POST http://localhost:3000/file/upload
router.post('/upload', async (ctx,next) => {
	let files = ctx.request.files.file;//文件对象
	let pathname = ctx.request.body.pathname;//文件需存储的路径
	let user = ctx.request.body.user;//上传者
	let flag = false;
	
	//判断单文件还是多文件
	if (files.length === undefined) {// 上传单个文件,它不是数组,只是单个的对象
		flag = false;
	} else {
		flag = true;
	}

	let p = [];//promise数组
	if (flag) {// 多个文件上传
    	for (let i = 0; i < files.length; i++) {
    		let f1 = files[i];
    		p.push(uploadFiles(f1,pathname,user));
    	}
    } else {
    	p.push(uploadFiles(files,pathname,user));
    }

    await Promise.all(p).then(res=>{//所有promise执行完后执行then
    	ctx.body = {
    		code: 0,
    		message: '上传成功',
    		res:res
    	};
    });
});

其中,uploadFiles函数如下:

const fs = require('fs');
const path = require('path');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

const uploadFiles = function(file,pathname,user) {
	const filePath = Config.StorageRoot;
	let fileReader, fileResource, writeStream;
	  
	return new Promise((resolve,reject)=>{
		fileResource = filePath + pathname +`${file.name}`;// 组装成绝对路径
		let extname = path.extname(fileResource);//文件后缀
		fs.exists(fileResource,function(exist){
			if(exist){//文件已存在
				resolve({
					status:'already',
					name:`${file.name}`
				})
			}else{
				MongoDB.insertMsg({//数据库中插入记录 
				    path:filePath + pathname,
				    name:file.name,
				    type:'file',
				    size:file.size,
				    suffix:extname,
				    uploader:user,
				    uploadtime:new Date().getTime(),
				    downloadcount:0
			    })
			    // 读取文件流
			    fileReader = fs.createReadStream(file.path);
			    // 使用 createWriteStream 写入数据,然后使用管道流pipe拼接
			    writeStream = fs.createWriteStream(fileResource);
			    fileReader.pipe(writeStream);
			}
	    })
	})
};

module.exports = uploadFiles;
文件下载

文件下载首先搜索数据库是否存在该文件,若存在,则更新下载次数字段,且根据路径返回文件。

//文件下载 GET http://localhost:3000/file/download/文件名?path=..&user=...
router.get('/download/:name', async (ctx,next) => {
	let name = ctx.params.name;
	let path = ctx.query.path;
	let user = ctx.query.user;
	const pathname = Config.StorageRoot + `${path}${name}`;
	let res = await MongoDB.findMsg({key:['path','name'],value:[Config.StorageRoot+path,name]});
	if(res.length > 0){//存在该文件
		MongoDB.updateMsg({//更新数据库下载次数
			wherekey:['_id'],
			wherevalue:[res[0]._id],
			updatekey:['downloadcount'],
			updatevalue:[++res[0].downloadcount]
		})
		ctx.attachment(pathname);
		await send(ctx, pathname);//发送文件
	}
});

koa-send帮你下载文件,仅需两行。

文件删除

直接上代码:

//删除文件 DELETE http://localhost:3000/file/delete/文件名?path=..&user=...
router.delete('/delete/:name',async (ctx,next) => {
	let filename = ctx.params.name;
	let filepath = ctx.query.path;
	let user = ctx.query.user;
	let dres = deleteFile(filename,filepath);//删除函数
	dres.then(res=>{
		ctx.body = {
			code:0
		}
	}).catch(err=>{
		ctx.body = {
			code:1,
			message:err
		}
	})
});

deleteFile函数如下:

const fs = require('fs');
const path = require('path');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

const deleteFile = function(filename,filepath){
	let storagepath = Config.StorageRoot + `${filepath}${filename}`;
	let extname = path.extname(storagepath);
	
	MongoDB.deleteMsg({
		key:['path','name'],
		value:[Config.StorageRoot + `${filepath}`,`${filename}`]
	})

	return new Promise((resolve,reject)=>{
		if(fs.existsSync(storagepath)){
			fs.unlinkSync(storagepath);//同步删除文件
		}
		resolve();
	})
}

module.exports = deleteFile;
分页获取文件夹下的所有文件信息

上代码:

//获取文件夹下所有文件 POST http://localhost:3000/file/getAll
router.post('/getAll', async (ctx,next)=>{
	const rb = ctx.request.body;
	ctx.response.body = await searchAllFiles(rb);//搜索路径下的所有文件
});

前端有过滤功能,根据条件筛选文件。searchAllFiles函数如下:

const fs = require('fs');
const path = require('path');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

const searchAllFiles = async function(req){
  //过滤搜索的参数包括:
  let pathname = req.path,
  index = req.index,
  num = req.num,
  suffix = req.suffix,
  type = req.type,
  name = req.name,
  uploader = req.uploader;
  
  let filepath = Config.StorageRoot+pathname;
  let key = ['path'],value = [filepath];

  if(suffix != ''){
    key.push('suffix');
    value.push(suffix[0]);
  }
  if(type != ''){
    key.push('type');
    value.push(type[0]);
  }
  if(uploader != ''){
    key.push('uploader');
    value.push({$regex:`${uploader}`,$options:"i"});
  }
  if(name != ''){
    key.push('name');
    value.push({$regex:`${name}`,$options:"i"});
  }

  let len = await MongoDB.findMsg({key:key,value:value,limit:200000});//文件总数
  let res = await MongoDB.findMsg({key:key,value:value,limit:num,skip:index});

  return {
    len:len.length,
    res:res
  } 
}

module.exports = searchAllFiles;

文件夹路由

directory.js结构

该路由仅解决文件夹处理,及创建与删除,和file.js功能类似,最大的差别在于删除文件夹需要用到迭代。结构如下:

const router = require('koa-router')();
const createDirectory = require('../lib/createDirectory');//创建文件夹函数
const deleteDir = require('../lib/deleteDir');//删除文件夹函数
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

//判断是否登录
router.use(async (ctx,next)=>{
	if (!ctx.session.user) {
		ctx.status = 403;
	}
	await next();
});

//具体内容...

module.exports = router;
文件夹创建

上代码:

//创建文件夹 POST http://localhost:3000/directory/create
router.post('/create',async (ctx)=>{
  const rb = ctx.request.body;
  let res = createDirectory(rb.path,rb.name,rb.user);
  ctx.response.body = {res:res};
})

createDirectory代码如下:

const fs = require('fs');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

const createDirectory = function(path,name,user){
  let file = Config.StorageRoot + path + name;
  
  try{
    fs.accessSync(file,fs.constants.R_OK);//已存在
    return false;
  }catch(e){//不存在
    MongoDB.insertMsg({
      path:Config.StorageRoot + path,
      type:'directory',
      name:name,
      uploader:user,
      uploadtime:new Date().getTime()
    });
    fs.mkdirSync(file);//本地创建文件夹
    return true;
  }
}

module.exports = createDirectory
文件夹删除

上代码:

//删除文件夹 DELETE http://localhost:3000/directory/delete/文件夹名?path=...&user=...
router.delete('/delete/:name',async (ctx,next) => {
	let dirname = ctx.params.name;
	let dirpath = ctx.query.path;
	let user = ctx.query.user;
	let dres = deleteDir(dirname,dirpath);//删除文件夹函数
	dres.then(res=>{
		ctx.body = {
			code:0
		}
	}).catch(err=>{
		ctx.body = {
			code:1,
			message:err
		}
	})
	await next();
});

deleteDir函数如下:

const fs = require('fs');
const path = require('path');
const Config = require('../config.js');
const deleteAll = require('./deleteAll');

const deleteDir = function(dirname,dirpath){
	let dirpathStorage = Config.StorageRoot + `${dirpath}${dirname}`;
	return new Promise((resolve,reject)=>{
		deleteAll(dirpathStorage,1);
		resolve();
	})
}

module.exports = deleteDir;

迭代删除文件夹内部的文件,全部删除后再删除当前文件夹,deleteAll代码如下:

const fs = require('fs');
const path = require('path');
const Config = require('../config.js');
const MongoOpr = require('../lib/dbOpr');
const MongoDB = new MongoOpr('File');

const deleteAll = function(path,type) {
	let files = [];
	if(fs.existsSync(path)) {//路径存在
		files = fs.readdirSync(path);
		files.forEach(function(file, index) {//遍历当前文件夹下的文件,逐一删除
			var curPath = path + "/" + file;
			if(fs.statSync(curPath).isDirectory()) {//若是文件夹,则执行迭代deleteAll
				deleteAll(curPath,type);
			} else { //若是文件,直接删除
				if(type == 1){
					MongoDB.deleteMsg({//数据库中删除文件记录
						key:['path','name'],
						value:[path+'/',file]
					})
				}				
				fs.unlinkSync(curPath);//本地删除文件
			}
		});
		let p = path.substring(0,path.lastIndexOf('/')+1);
		let n = path.substring(path.lastIndexOf('/')+1);
		if(type == 1){
			MongoDB.deleteMsg({//删除当前文件夹
				key:['path','name'],
				value:[p,n]
			})
		}
		fs.rmdirSync(path);//本地删除当前文件夹
	}
};

module.exports = deleteAll;

文件和文件夹的删除应该设置用户的权限,可以在后端获取用户的角色,再判断是否具有删除权限。当然,简单做就直接在前端判断即可。

下期预告

本文带大家实操了后端文件的读写和CURD功能实现,那么现在引出一个问题:

如果上传的是图片,而且图片体积很大,前端如果快速加载出来呢?

这就涉及到图片的压缩和裁剪问题了(缩略图生成)。下一期,我将教大家如何在后端生成图片的缩略图,并且组织好原图与缩略图的存储位置。

期待你的再次光临~See U ✌️

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HouGISer

HouGiser需要你的鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值