使用path
一、关于node运行时的路径问题
如上图,在Code文件夹下的终端用node命令运行demo3.js文件是成功的。但是当我们换一种写法:
已经报错了。原因就是我们此时的终端是运行在桌面的,虽然我们的本意是用node运行桌面下的Code文件夹中的demo3.js文件,但是始终应该注意的是终端的打开位置,当我们用相对路径指定了file1.txt后,node会将终端打开的路径,即桌面的路径,与指定的相对路径动态拼接起来,此时报错是必然的。
解决方法是使用绝对路径,但绝对路径的拼接极易出错,在这不再赘述。此时path模块成了另一个解决方案。
二、 path
- 路径的拼接
const path = require('path')
console.log(__direname) // C:\Users\86153\Desktop\code 即当前文件所处的目录
console.log(path.join(__dirname, './file1.txt')) // C:\Users\86153\Desktop\code\file1.txt
console.log(path.join(__dirname, '/file1.txt')) // C:\Users\86153\Desktop\code\file1.txt
console.log(path.join(__dirname,'/file1.txt','../')) // C:\Users\86153\Desktop\code\
如上面代码所示,path的join方法可以把多个路径字符串拼接起来,而使用 . . / 可以回退到上一级目录下。
- 文件名和文件扩展名
const filePath = 'file1.txt'
console.log(path.extname(filePath)) // .txt
console.log(path.basename(filePath, '.txt')) // file1
// filePath如果是C:\\Users\\86153\\Desktop\\code\\file1.txt得到的结果也相同
文件
写在最前:对文件(夹)的操作方法,尽量选择异步的写法。因为Js是一门单线程的语言,在服务端如果使用同步的写法,若待操作的文件过大,很可能会阻塞服务器。
一、获取文件(夹)的详情信息以及文件(夹)重命名
- stat,此方法可以对文件或者文件夹进行操作。
fs.stat('./file1.txt', (err, data) => {
console.log(data) // 文件(夹)信息,包含创建时间等信息
console.log(data.isFile()) // 是文件返回true,文件夹返回false
console.log(data.isDirectory()) // 是文件返回false,是文件夹返回true
})
- rename,此方法可以对文件或者文件夹进行重命名。
// 对文件:
fs.rename('./test1.txt', './test2.txt', (err) => {
console.log(err)
})
// 对文件夹不需要加上后缀名,代码省略
二、读
// 异步的写法
fs.readFile('./file1.txt', 'utf-8', function (err, dataStr) {
if (err) {
console.log(err)
}
console.log(dataStr) // 输出file1.txt中的内容
})
// 同步的写法
fs.readFileSync('./file1.txt', 'utf-8')
三、写
// 异步的写法
fs.writeFile('./file1.txt', '待写入的内容', (err) => {
if (err) {
console.log(err)
}
})
// 同步的写法
fs.writeFileSync('./file1.txt', '待写入的内容')
注意,以上两个方法是在覆盖待操作文件原有内容的基础上进行写入的。想要在待操作文件原有内容的基础上追加写入新内容应该使用appendFile。
// 异步
fs.appendFile('./file1.txt', '追加写入的内容', err => console.log(err))
// 同步
fs.appendFileSync('./file1.txt', '追加写入的内容')
四、创建
在使用writeFile或者writeFileSync方法时如果待操作的文件不存在,会自动创建,因此可以使用此特征来创建文件。
fs.writeFile('./file2.txt', '', (err) => {
if (err) {
console.log(err.message)
}
console.log('file2.txt成功创建')
})
五、删除
// 异步
fs.unlink(path.join(__dirname, './file3.docx'), err => console.log(err))
// 同步
fs.unlinkSync('./file2.txt')
流
在读写大文件时,使用的更多的是stream,原因同样是因为Js是单线程语言,防止代码阻塞。stream是nodejs提供的一个仅在服务端可用的模块,目的是支持“流”这种数据结构。
一、读
const rs = fs.createReadStream('./file1.txt', 'utf-8') // 创建一个文件流
// 事件绑定
rs.on('data', chunk => {
// data事件有可能被触发多次, 因为chunk代表了整个文件流中的一小部分
console.log(chunk)
})
rs.on('end', () => {
console.log('ok')
})
rs.on('error', (err) => {
console.log('err:' + err)
})
二、写
const ws = fs.createWriteStream('./file2.txt', 'utf-8')
ws.write('bsd\n')
ws.write('bsd\n')
ws.write('bsd\n')
ws.write('bsd\n')
ws.write('bsd\n')
注意,当对一个创建了一个新的写文件流的时候,写文件流所操作的文件的内容会被覆盖,但是写文件流的write方法是对待操作文件进行追加写入。
三、管道
pipe常用于大文件之间的复制,也常见于在前后端交互中使用。
const ws = fs.createWriteStream('./file2.txt')
const rs = fs.createReadStream('./file1.txt')
rs.pipe(ws) // 把file1的内容写到file2里面
文件夹
一、读
fs.readdir('./files', (err, data) => {
console.log(data)
// data是一个数组,里面包含了files文件夹下的文件的文件名
})
二、创建
fs.mkdir('./files',(err) => {
if(err){
console.log(err)
}
})
// 会在当前js文件所在的目录下创建files文件夹
三、删除
fs.rmdir('./files', err => console.log(err))
但是如果待删除的文件夹不为空则会报错,应该先删除里面的文件:
fs.readdir('./files', (err, data) => {
data.forEach(item => {
fs.unlink('./files/' + item, err => { console.log(err) })
})
})
fs.rmdir('./files', err => console.log(err))
前后端交互
此处前端使用Vue3,后端使用Egg.js,实现了用户头像更新的功能,经供参考。
前端代码:
// 注意请求头的Content-Type的值应为multipart/form-data
function changePic() {
const fileInput = document.getElementById("file-input");
fileInput.click(); // 触发点击事件
}
function submitPic(e) {
if (e.target.files.length === 0) {
return;
}
const formData = new FormData();
formData.append("file", e.target.files[0]); //传递给后端的参数
// 与后端对接的接口,此处使用了Axios
userPicUpdate(formData, $store.userID).then(() => {
const userImg = document.getElementById("user-img");
const reader = new FileReader();
reader.onload = function () {
userImg.src = reader.result; // 现在前端渲染用户更新的头像,减少网络请求的次数
};
reader.readAsDataURL(e.target.files[0]);
ElMessage({
showClose: true,
message: "头像更新成功。",
type: "success",
});
mySwitch.value = false;
});
}
后端代码:
很丑陋的代码,毫无逻辑性可言。
async changeUserPic() {
const { ctx } = this;
const oldPicStatic = await ctx.service.user.getUserPic(ctx.body.userID);
const newPic = './app/public/user-images/' + ctx.body.userID + '.jpg';
const file = ctx.request.files[0];
if (oldPicStatic !== 'http://127.0.0.1:7002/public/user-images/default.jpg') {
fs.unlink(newPic, err => {
if (err) throw err;
console.log('File has been deleted!');
});
} else {
const staticPicPath = 'http://127.0.0.1:7002/public/user-images/' + ctx.body.userID + '.jpg';
await ctx.service.user.changePic(ctx.body.userID, staticPicPath);
}
fs.writeFile(newPic, '', err => {
if (err) throw err;
});
// 关键点!
const readStream = fs.createReadStream(file.filepath);
const writeStream = fs.createWriteStream(newPic);
readStream.pipe(writeStream);
ctx.body = {
status: 200,
msg: 'ok',
};
}