Node - fs模块下的文件系统(五)

1,321 阅读5分钟

“Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine”官网的一句介绍大致是说“Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时”,简单来说就是一个能让JS运行在服务端的环境。

前言

最近公司事比较多,有点耽搁了,但依旧坚持着周更,不管写得如何,不放弃继续努力~

今天主题讲点有用的,前端上传文件应该是个比较常见的事情了,那么后端要如何接收呢?今天就来玩玩这个事情吧。

Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。(这句话可不是我说的,官方话语,反正其实就是让我们学习fs模块就对了,管那么多。。。)

相关API罗列

Node中和文件系统相关的API基本都有其同步与异步方法对应。

文件-读与写

既然是对文件的操作,那我们先来聊聊对一个已存在的文件的读写操作吧,话不多说,直接上代码,太 easy 了,多说你们又说我划水了。

我们创建 1.txt 文件

Hello, World.
你好, 世界.

创建 test.js 文件

const fs = require('fs');
fs.readFile('./1.txt', 'utf8', (err, data) => {
  console.log(data); // Hello, World  你好, 世界
});

在cmd中执行 node test.js 我们就简单的读取到了一个文件了, 如此简单就不必多说了,我们接下来往这个 1.txt 文件写入东西吧。

我们稍微修改一下 test.js的代码

const fs = require('fs');
fs.writeFile('./1.txt', '我是写入的数据', err => {
  if(err) {
    console.log('读取文件失败')
  }else {
    console.log('写入成功') // 写入成功
  }
})

再次执行node test.js 查看 1.txt 文件

同步方法:

// 同步写入文件
var str = '我是写进来的';
fs.writeFileSync('./1.txt', '我是写入的数据', err => {
  console.log('写入文件失败') // 错误回调
})

// 同步读取文件
try{
  var data = fs.readFileSync('./1.txt', 'utf-8');
  console.log(data)
}catch (e) {
  console.log('读取文件失败')
}

文件-内容追加

细心的铁子可能发现了,我们在给文件写入数据的时候,是以覆盖的形式写入的,那么如何用追加的形式的,Node自然也是提供了相关API了。

再改 test.js 文件

const fs = require('fs');
fs.appendFile('./1.txt', '*** 我是追加进来的数据 ***', err => {
  if(err) console.log('追加数据失败')
})

查看 1.txt 文件

同步方法:

const fs = require('fs');
fs.appendFileSync('./1.txt', '*** 我是追加进来的数据 ***')

文件-创建

Node并没有直接提供创建文件的相关API,我们能直接利用写入的API来完成,默认写入的API如果所填写的文件不存在,则会帮忙创建

const fs = require('fs');
fs.writeFile('./111.txt', '我是写入的数据', err => {
  if(err) {
    console.log('读取文件失败')
  }else {
    console.log('写入成功') // 写入成功
  }
})

文件-删除

const fs = require('fs');
fs.unlink('./1.txt', err => {
  if(err) console.log('删除文件失败')
})

同步方法:

const fs = require('fs');
fs.unlinkSync('./1.txt')

目录-创建

如果是已经存在的目录,不会重复创建

const fs = require('fs');
fs.mkdir('./public', err => {
  if(err) console.log('创建目录失败')
})

同步方法:

const fs = require('fs');
fs.mkdirSync('./public')

目录-读取

我们在 public 目录下新建 2.txt 文件与 newPublic 目录

const fs = require('fs');
fs.readdir('./public', (err, files) => {
  console.log(files); // [ '2.txt', 'newPublic' ]
})

同步方法:

const fs = require('fs');
console.log(fs.readdirSync('./public')); // [ '2.txt', 'newPublic' ]

目录-删除

目录下有文件/目录的话无法删除,只能删除空目录

const fs = require('fs');
fs.rmdir('./public', err => {
  if(err) console.log('删除目录失败')
})

同步方法:

const fs = require('fs');
fs.rmdirSync('./public')

文件/目录-获取信息

const fs = require('fs');
fs.stat('./1.txt', (err, stats) => {
  if (err) {
    return console.log('获取文件信息失败');
  }
  console.log(stats);
  console.log("是否为一个文件:" + stats.isFile());
  console.log("是否为一个目录:" + stats.isDirectory());
});

方法描述
stats.isFile()如果是文件返回 true,否则返回 false。
stats.isDirectory()如果是目录返回 true,否则返回 false。
stats.isBlockDevice()如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice()如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink()如果是软链接返回 true,否则返回 false。
stats.isFIFO()如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。
stats.isSocket()如果是 Socket 返回 true,否则返回 false。

同步方法:

const fs = require('fs');
const stat = fs.statSync('./1.txt')
console.log(stat)

文件/目录-是否存在

const fs = require('fs');
fs.exists('./1.txt', res => {
  console.log('文件/目录存在:' + res); // 文件/目录存在:true
})

同步方法:

console.log('文件/目录存在:' + fs.existsSync('./1.txt')); // 文件/目录存在:true

文件/目录-重命名与移动

我们直接给文件/目录改名字

const fs = require('fs');
fs.rename('./1.txt', './2.txt', err => {
  console.log(err)
})
fs.rename('./public', './newPublic', err => {
  console.log(err)
})

也可以利用该API实现文件位置的移动

fs.rename('./2.txt', './public/1.txt', err => {
  console.log(err)
})

同步方法:

const fs = require('fs');
fs.renameSync('./public/1.txt', './2.txt') // 我们把文件移动回来

接收前端的上传的图片

我们简单来实现一下前端上传图片(FormData形式)的时候,原生Node是需要如何来处理的。

  • 新建index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<body>
<input type="file" id="file" name="myfile" />
<button>点击</button>

<script>
  $("button").click(() => {
    var file = document.getElementById("file").files[0];
    var form = new FormData();
    form.append("file", file);
    form.append('username', 'username');
    form.append('password', 'password');
    $.ajax({
      url: 'http://localhost:8888',
      type: "POST",
      data: form,
      async: false, //异步
      processData: false, // 让jq不对formData对象进行处理
      contentType: false, // 自动加上对应的contentType
      success: (result) => {
        console.log(result)
      }
    });
  });
</script>
</body>
</html>
  • 服务端处理
const fs = require('fs');
const http = require('http');

http.createServer((req, res) =>{
  // 允许跨域(不急, 后面跨域会专门来说)
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild");
  res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  res.setHeader("X-Powered-By","3.2.1");
  res.setHeader("Content-Type", "application/json;charset=utf-8");

  let chunks = [];
  let size = 0;
  let buffer = null;
  let offsets = [];
  req.on('data', (chunk) => {
    // chunk默认为一个二进制数据流, 该方法会执行多次, 需要我们手动累加二进制数据
    chunks.push(chunk);
    size = chunk.length; // 获取相关Buffer流的大小
  });

  req.on('end',() => {
    // 合并相关Buffer流
    buffer = Buffer.concat(chunks, size);
    for(let i = 0;i < buffer.length; i++){
      if(buffer[i].toString() == 13 && buffer[i+1].toString() == 10){
        offsets.push(i);
      }
    }
    // 获取文件名
    let name = buffer.slice(offsets[0], offsets[1]).toString().split(';')[2].split('=')[1];
    let filename = name.split("\"");
    // 写出文件
    let data = buffer.slice(offsets[3] + 2, offsets[offsets.length - 2]);
    fs.writeFileSync('./' + filename[1], data);
  });

  res.end('文件上传成功');
}).listen('8888');
console.log('Server running at http://localhost:8888');

执行后的结果,当前目录下就有上传的图片了。。。nice

  • 关于Buffer类型,在JavaScript语言自身只有字符串数据类型, 没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。因此在Node中,定义了一个Buffer类,该类用来创建一个专门存放二进制数据的缓存区。 www.runoob.com/nodejs/node…
  • 关于offsets偏移量,我们把buffer流toString()打印一下,在控制台大概能看到一大堆乱七八糟的东西,但是我们真正要取的只有 PNG 下面那部分,所以需要搞点偏移量,取到图片就行。包括文件的名字也能从中获取。
  • 最后说一下,其实Node来处理文件上传已经有很多npm包能快速、方便的上传了,这里简单讲一下,就是希望对这其中有个大概的了解,就这样子吧,嘿嘿~

个人感觉自己文笔不是很好,是个比较偏实践类的人,但偶尔也羡慕那些写文写得好的大佬,也想学学大佬们写些有趣的东西。所谓的菜鸟的自我修养,就是向大佬的看齐,即使当作学习的笔记也罢,反正就是把自己知道的东西说出来而已,这是我一个写文的念头。同时也我希望自己是个能以最简洁的言语讲清一个事要点的人,所谓读书在精不在多怎么一个方式和大家见面。