文章目录
本话概要
又见面啦~9102年马上就要结束了,我要抓住时间的尾巴多出几篇博文!!等待候补车票兑现的第n天😭
本期涉及的主要内容包括:koa后端处理文件及文件夹的读写
和增删改查
功能。本期的代码量很大,小伙伴们跟上啊~奥里给
初识文件管理系统
文件共享平台的核心就是文件云管理。需要满足多用户共同上传、下载、删除文件/文件夹。那么从后台的角度,我们需要解决哪些技术问题?
- 接收文件流转云服务器的本地文件
- 读取云服务器的本地文件转文件流回传
- 同时上传多文件问题
- 创建、删除文件/文件夹
- … …
想象一下平时我们在自己电脑上所能实现的文件操作,现在的区别就是多个用户可以同时对一台电脑的文件做相同的操作。下面我们就进入正题,教大家如何搭建文件处理路由。👇
文件管理路由实现
数据库文件表设计
直接上图。顺便安利一下免费表设计利器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 ✌️