首先解释一下为什么我这个标题都长这么随意,一般别人都是按内容分模块的来写。其实是因为我学习都是按照技术点来的,如果断断续续的看印象不深,因此一次性看的内容比较多,不好区分什么是重点,所以就不纠结标题了,如果觉得好可以看看专栏里面的其他内容。好啦,废话不多说了,直接开始吧。
node.js中内置了fs文件操作模块,含有基本的文件增删查改(通常简称为crud,也就是create,remove,update,delete,由于文件修改和文件读写都需要时间,因此都是异步操作)。下面主要展示常用的api:
(1)创建文件mkdir
:
const fs = require("fs");
fs.mkdir("./logs", (err) => {
if (err) throw err;
console.log("创建文件目录成功");
});
需要注意的是,这里遵循的是错误优先规则,因此首先需要检测err才能执行之后的操作,比如:如果两次重复创建,err遍会生效。
(2)修改目录名rename
(同样错误优先):
const fs = require("fs");
fs.rename("./logs", "log", (err) => {
if (err) throw err;
console.log("文件目录名修改成功!");
});
(3)删除文件夹rmdir
(错误优先,只能删除空文件夹):
const fs = require("fs");
fs.rmdir("./log", (err) => {
if (err) throw err;
console.log("文件夹删除成功!");
});
(4)查找文件夹readdir
(错误优先,读取目录下的文件信息变成了数组,只能往下读一级):
const fs = require("fs");
fs.readdir("./log", (err, res) => {
if (err) throw err;
console.log("输出问价夹中的内容:", res);
});
(1)向文件中书写内容writeFile
:
const fs = require("fs");
fs.writeFile("./logs/log.log", "hello\nworld!", (err) => {
if (err) throw err;
console.log("数据写入成功!");
});
需要注意的是,如果没有文件,那么会直接创建文件并写入内容,但是如果文件前面的目录也不存在,会直接抛错。重复写入相同文件会直接覆盖文件中原有的内容。
(2)向文件中写入内容appendFile
:
fs.appendFile("./logs/log.log", "hello world!", (err) => {
if (err) throw err;
console.log("追加内容成功!");
});
(3)读取文件中的内容readFile
:
fs.readFile("./logs/log.log", (err, res) => {
if (err) throw err;
console.log("输出数据:", res.toString());
});
需要注意的是,读取文件之后的内容为二进制数据,需要进一步使用toString
函数来转化。其次文件读写有两种函数(同步readFileSync
和异步readFile
),Synchronous(同步的)
,Asynchronous(异步的)
,异步处理写法如下:
const content = fs.readFileSync("./logs/log.log");
console.log(content.toString());
console.log("waiting...");
在文件系统中,fs还能够利用promise将同步转化成异步操作:
最后用上上面的函数可以得到一个递归搜索文件内容的函数:
const fs = require("fs");
const targetDir = "../file-manager";
(function searchDir(dir) {
let dirList = new Array();
fs.readdir(dir, (err1, res1) => {
if (err1) throw err1;
dirList = res1;
if (dirList.length) {
dirList.forEach((item, index) => {
let subdir = dir + "/" + item;
fs.stat(subdir, (err2, res2) => {
if (err2) throw err2;
if (res2.isDirectory()) {
searchDir(subdir);
} else {
console.log("输出文件目录:", subdir);
console.log(fs.readFileSync(subdir).toString());
}
});
});
}
});
})(targetDir);
(4)读取文件中的内容unlink
:
const fs = require("fs");
fs.unlink("./logs/log.log", (err) => {
if (err) throw err;
console.log("删除文件成功!");
});
结合上面的函数,能够得到如下的文件清空程序(目录中内容非空无法删除):
const fs = require("fs");
const targetDir = "./logs";
(function clearDir(dir) {
fs.readdir(dir, (err1, res1) => {
if (err1) throw err1;
const dirList = res1;
if (dirList.length) {
dirList.forEach((item, index) => {
let tempDir = dir + "/" + item;
console.log("当前需要处理的目录:", tempDir);
fs.stat(tempDir, (err2, res2) => {
if (err2) throw err2;
if (res2.isDirectory()) {
clearDir(tempDir);
} else {
fs.unlink(tempDir, (err3) => {
if (err3) throw err3;
});
}
});
});
} else {
fs.rmdir(dir, (err) => {
if (err) throw err;
});
}
});
})(targetDir);
逻辑差了点,这个只能删除最深一层的数据。如果你想写的更秀一点,直接给出一个目录删除全部的子文件夹和目录,欢迎在评论区指点我一波(暴力多次深度遍历除外,这个太费时了,不太科学)。通过查阅api文档发现有实现这种递归删除的函数(害,算法不行,还得靠大佬):
fs.rm(targetDir, { recursive: true }, (err) => {
if (err) throw err;
console.log("文件删除成功!");
});
const fs = require("fs");
fs.watch("./log.txt", (res) => {
console.log(res);
console.log("文件发生改变");
});
主要能够监听两种情况,rename
和change
,移动文件位置、重命名(前两个都属于rename事件)或者修改内容都能够触发watch监听事件。{ recursive: true }
可以产生递归监听效果。需要明确的是还有一种监听方法watchFile
。两者相比较而言,内部实现的逻辑不一样,watch函数的跨平台性有一定的弱点,主要支持windows和macos
。建议优先使用watchFile
。对于目录的监听主要监听文件目录下的文件创建和删除,被监听目录的名称变化,而不会监听子文件中内容的变化,响应之后会产生回调函数类似于fs.stat
函数的前后两次调用,watchFile
还能够设置轮询时长:
import { watchFile } from 'fs';
watchFile('message.txt', (curr, prev) => {
console.log(`更改后的stat信息: ${curr.mtime}`);
console.log(`更改前的stat信息: ${prev.mtime}`);
});
例如利用文件的读写流实现简单的文件复制:
const fs = require("fs");
let readData = fs.createReadStream("./index.html");
let writeData = fs.createWriteStream("./index-back.html");
readData.pipe(writeData);
对于这种数据流,还可以进行中间操作,就相当于利用一根管子,将一个文件中的数据流传入另一个文件里面,中间可以进行“过滤”等操作。
const fs = require("fs");
const zlib = require("zlib");
const gzip = zlib.createGzip();
let readData = fs.createReadStream("./index.html");
let writeData = fs.createWriteStream("./index-back.gzip");
readData.pipe(gzip).pipe(writeData);
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("你如何看待node.js?", (answer) => {
console.log(`感谢你的宝贵意见:${answer}`);
});
程序执行效果如下:
有点像命令行的味道,有类似于js的prompt弹出框
,扩展了node.js的业务面。
直接给出一个例子吧,具体的加密方式还得看实际的要求(下面使用的是sha256加密,然后转化成了十六进制)。
const crypto = require("crypto");
const password1 = "admin123";
console.log(
"输出加密之后的数据:",
crypto.createHash("sha256").update(password).digest("hex")
);
node.js内置模块总算是看了个大概,接下来就是比较结合实践的路由了。和其他后端一样,node.js路由就是希望处理url,为用户暴露出接口。首先给出一个简单的例子:
const http = require("http");
const https = require("https");
const fs = require("fs");
const server = http.createServer((req, res) => {
const urlString = req.url;
switch (urlString) {
case "/html":
const content = fs.readFileSync("./index.html");
res.write(content);
res.end();
break;
case "/test.js":
const js = fs.readFileSync("./test.js");
res.write(js);
res.end();
break;
case "/lovely.jpg":
const image = fs.readFileSync("./lovely.jpg");
res.write(image);
res.end();
break;
default:
res.write("This is default content.");
res.end();
}
console.log("你的请求地址是:", req.url);
});
server.listen(8080, () => {
console.log("localhost:8080");
});
解释一下这里为什么写了这么多关于资源加载的,其实如果通过后端,就必须将这些文件封装成数据流发送给前端形成前端请求的接口。但是不可能为每一个文件都写一个接口。分析接口之间的特点,我们可以只封装文件读取,直接发送给前端交由浏览器来自动辨别我们传输的数据类型。但是,这样是不太清晰的。
解决方法是使用mime
依赖,安装依赖npm install mime -S
,mime
中的getType
函数能够直接根据后缀名快速判断文件数据传输时候的类型,然后修改文件获取的代码:
const urlString = req.url;
const type = urlString.substring(urlString.lastIndexOf("."));
const targetType = mime.getType(type);
const file = fs.readFileSync(`.${urlString}`);
res.writeHead(200, {
"content-type": targetType,
});
res.write(file);
res.end(file);
代码倒好说,主要是搭建的目录形式,其实和thinkPHP后端框架差不多:
附上程序代码:
const fs = require("fs");
const http = require("http");
const path = require("path");
const mime = require("mime");
function readData(dir, res) {
if (fs.existsSync(dir)) {
dirStr = String(dir);
const targetType = mime.getType(dirStr.substring(dirStr.lastIndexOf(".")));
const content = fs.readFileSync(dir);
res.writeHead(200, {
"content-type": targetType + ";charset=utf-8;",
});
res.end(content);
} else {
res.end("The file or floder not found!");
}
}
const server = http.createServer((req, res) => {
const pathDir = path.resolve(__dirname, "./public") + req.url;
fs.stat(pathDir, (err1, res1) => {
if (err1) throw err1;
if (res1.isDirectory()) {
readData(path.resolve(pathDir, "/index.html"), res);
} else readData(pathDir, res);
});
});
server.listen(8080, () => {
console.log("localhost:8080");
});