原生nodejs和javascript实现上传下载


theme: fancy

一. 引言

背景: 随着语言技术的发展和工作需求,为了实现方便,快速,高效的开发体验,越来越多的框架,中间件等工具层出不穷,像nodejs下的express、egg、koajavascript下的react、vue等,的却这些工具的出现让我们的开发越来越高效,上手也越来越快,但是,这反而让人们失去了学习语言的原理,并且自己去思考的动力,遇到需求直接找一些已经开发好的工具直接使用,虽然会用,但是从不知道它背后的逻辑。

好了,废话不多说,通过本节学习,你将学会处理各种类型的数据传输,无论是文字,图片还是其他文件,都通通适用。

在学习之前让我们先去了解以下几个知识点

javascript:
         - File
         - Blob
nodejs  :
         - Buffer

二. File

从字面意义来看,file也就是我们要上传的文件,在前端中,我们唯一访问文件的情况,就是使用input标签的type="file",用户选择文件后会返回一个FileList对象。我们来看一下,就以上传图片为例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcqffo0H-1658473722806)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98875a9b413246aa92a142736040ffb6~tplv-k3u1fbpfcp-watermark.image?)]
FileList是一个类数组类型,其中存放着用户选择的文件,用户可以选择多个文件,所以使用类数组保存,在这我们只上传一个图片,所以可以通过FileList[0]来访问具体文件File,在File中有如图所示的属性,再此就不做介绍了。

三. Blob

MDN的解释来说,Blob对象表示一个不可变、原始数据的类文件对象。正常来说,我们在保存数据时,都是使用我们熟悉的utf-8的格式,但是计算机在处理数据时,更熟悉的是二进制数据,并且我们在操作非文本语言时,无法用utf-8的形式来操作数据,所以我们可以用Blob以二进制数据的形式来处理任意数据。因此我们可以把Blob当作一个存储并且操作二进制数据的一个对象。

1. 构建Blob

通过构造函数的形式来创建一个Blob对象,第一个参数为array,第二个为一个options

const blob = new Blob(array, options)

2. 使用Blob

首先我们先来初步了解以下Blob的一些方法和属性

  • size:访问Blob的大小,单位为字节
  • type: 文件的 MIME 类型
  • arrayBuffer(): 返回一个Promise对象,用二进制来访问数据
  • text(): 返回一个Promise对象,用 utf-8 格式来访问数据
  • slice(): 类似于数组的slice 方法,来截取一段指定范围的新Blob对象
const blob = new Blob(['点个赞吧哈哈'],{type:'text/plain'})
console.log(blob.size, 'size')
console.log(blob.type, 'type')
blob.text().then(console.log)
blob.arrayBuffer().then(console.log)
const blobCopy = blob.slice(0, 6)
blobCopy.text().then(console.log)

打印结果如下:我们截取了6个字节blob,也就是点个两个字,对字符串来说,该方法好像多此一举,String.prototype中已经实现,但是在传输大文件时,我们可以很好的利用该功能实现分片传输。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1lymq5E-1658473722810)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d364744301cc450a9f663e71b10fd1c5~tplv-k3u1fbpfcp-watermark.image?)]

四.Buffer

Buffernodejs中内置的一个对象,它与前边提过的Blob类似,用来处理像图片,文件这样的数据,它的操作方式跟数组类似,可以通过下标访问,其中数据是用两位的十六进制来保存的。

1. Buffer 内存分配

首先要了解的一点是,Bufferjavascriptc++相结合的一个模块,所以,它的性能远远比单独javascript要快的多。除此外,Buffer内存是由C++申请,而不是由V8来分配,属于堆外内存。而js只控制其内存调度,也就是使用。
我们来了解一个新的概念slabslab是由C++申请的一块8kb的内存,可以把它理解为内存的最小调度单位,每次js使用Buffer来存储一段数据时,会将数据存到同一个slab中,直到下次Buffer的使用空间大于slab剩余的大小时,C++继续申请一块新的slab
优点:如果每次使用一点Buffer就申请内存,频繁操作为造成严重性能问题,使用动态内存管理类机制,很好的解决了该问题。

2. Buffer 的使用

const buf = Buffer.alloc(1024, '来个关注吧', 'utf-8')
console.log(buf)
console.log(buf.length)
console.log(buf[0])

我们看下如下打印结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZ0NGPzq-1658473722812)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/888710a51f284c4eae82e23ecb8c9e6d~tplv-k3u1fbpfcp-watermark.image?)]

方法和参数

  • alloc: 接收三个参数,分别是申请空间大小(单位是字节),数据填充内存,编码方式
  • length: 占用空间大小
  • 打印第一个字节,由于是两位十六进制,所以范围为 0 - ff,也就是 0 - 255
  • concat: 同数组的该方法类似,用来将每一个小段Buffer拼接起来
  • toString(encoding):该方法用来将buffer转为其他编码方式

了解了上边内容后,我们进入今天的主题,上传和下载,其所上边讲的一切都是为了数据在 tcp 通道中传输做准备。

五. 文件下载

我们先来理下思路,文件下载,肯定先由客户端发起下载请求,然后后端将文件传送到前端并下载,在此用到了两个知识点。\

  • node 使用 fs来读取文件
  • 客户端使用a标签的download属性实现实现下载
  • 使用URL.createObjectURL来创建可下载url

注:以上知识请大家自行了解,在此不做太多解释
直接上代码(完整代码,可直接复制测试一下,记得目录下放文件)

// client
<body>
    <button id="btn">文件下载</button>
    <h1 id="fileSize">文件大小:</h1>
</body>
<script>
    const btn = document.getElementById('btn')
    btn.onclick = function () {
        fetch('/down', {method: 'get'})
            .then(res => res.blob())
            .then(data => {
                getFileSize(data)
                fileDown(data)
            })
    }
    
    const fileDown = function (data) {
        const link = document.createElement('a')
        link.download = '1.jpg'
        link.href = URL.createObjectURL(data)
        link.style.display = 'none'
        link.text = '文件下载'
        document.body.appendChild(link)
        link.click()
        URL.revokeObjectURL(link.href)
        document.body.removeChild(link)
    }
    
    const getFileSize = function(blob){
        document.getElementById('fileSize').innerText = `文件大小:${Math.round(blob.size/(1024*1024))}MB`
    }
</script>
// sever
const http = require('http')
const fs = require('fs')
class App {
    source
    constructor() {
        this.source = {}
    }
    use(path, fn) {
        this.source[path] = fn
    }
}

app.use('/down', (req, res) => {
    const rs = fs.createReadStream('./1.zip', {
        highWaterMark: 1024 * 8
    })
    const data = []
    let size = 0
    rs.on('data', chunk => {
        size += chunk.length
        data.push(chunk)

    })
    rs.on('end', () => {
        const buf = Buffer.concat(data, size)
        res.setHeader('Content-Type', 'application/octet-stream')
        res.setStatus = 200
        res.setHeader('content-length', size)
        res.end(buf)
    })
})

http.createServer(function handleRequest(req, res) {
    if (app.source[req.url]) {
        app.source[req.url](req, res)
    } else {
        res.statusCode = 404
        res.end()
    }
}).listen(3000, () => {
    console.log('running')
})

看完代码可能有点懵逼,我来简单说一下:

  • client
    • 直接发起下载请求拿回返回数据
    • 执行方法getFileSize 将数据大小转为MB 显示到页面上(草率写了下)
    • 实现下载,这一步是最重要的,将获取到的数据流转为blob类型,然后利用URL.createObjuectURL创建一个指向该数据的url,并赋值个a标签的href属性,再利用download的属性,实现下载文件( 这里文件名我写死了,根据文件尾缀会生成不同类型文件,这一定要注意 )
  • sever
    • 处理路由我做了个简单封装用来注册路由,不要理会
    • 首先是文件读取,使用fscreateReadStream以数据流的形式读取文件,通过监听数据流读入,每次读取一个chunk都是一个小的Buffer,用数组将这些小段的Buffer收集起来,当监听到数据读取完毕,将小的Buffer拼接起来,一起发回前端。

以上就是文件下载的前后端实现,大家可以自己测试,可以下载任何类型文件,一定要记住修改生成文件尾缀。接下来我们说一下上传

六.文件上传

// client
<body>
    <input type="file" onchange="handleFileChange(this)">
</body>
<script>
    const handleFileChange = function (file) {
        const source = file.files[0]
        upload(source)
    }
    
     const upload = function (source) {
        const blob = new Blob([source], { type: source.type })
        fetch('/upload', {
            method: 'post',
            body: blob
        })
    }
</script>
// server
app.use('/upload', (req, res) => {
    const data = []
    req.on('data', chunk => {
        data.push(chunk)
    })
    req.on('end', () => {
        const buffer = Buffer.concat(data)
        fs.writeFile('./peg.jpg', buffer, (err) => {
            console.log(err)
        })
    })
    res.end()
})

简单阐述下思路:

  • client

    • 首先前端通过input标签获取用户要上传的文件,再将其改为Blob数据格式,此处其实不需要做更改,file对象本身实现了Blob对象的原型链继承,可以直接使用其方法
    • 使用fetch将数据发送
  • server

    • 通过req.on方法来监听数据的传输,同数据下载一下,此处读取的每一个chunk都是小的二进制流,保存在buffer中,当监听到数据全部传输完毕,使用Buffer.concat方法将所有小段数据组合
    • 最后使用fs.writeFileBuffer数据写入任意路径下,完美实现了文件上传

七. 结尾

使用以上方法,我们可以处理任何类型的文件,但是这里写的都是文件一次性上传,如果遇到大文件上传,就需要分片上传,大家可以利用BlobBuffer的方法slice,将数据分段上传,可以自己尝试,再此就不多说啦。有机会之后文章中在聊。
在学习任何语言中,我们一定不要急于求成,一定要先学习语言的原生方法特性,将基础打好,再去学习对应框架从而事半功倍。

### 回答1: 在 Node.js 中获取电脑屏幕亮度可以使用第三方库,例如 `brightness`。 安装 brightness 库: ``` npm install brightness ``` 然后,在你的代码中使用下面的方式获取电脑屏幕亮度: ``` const brightness = require('brightness'); brightness.get().then(level => { console.log(`Screen brightness is ${level}`); }); ``` 这样就可以获取当前电脑屏幕的亮度值了。注意,亮度值是一个浮点数,范围是 0 到 1。 如果你想要设置电脑屏幕亮度,可以使用 `brightness.set(level)` 方法。例如,将屏幕亮度设置为最大值: ``` brightness.set(1); ``` 希望这个答案对你有帮助。 ### 回答2: 使用原生node.js获取Mac电脑屏幕亮度可以通过执行Shell命令来实现。 首先,需要使用Node.js的内置模块child_process来执行Shell命令。代码如下: ```javascript const { exec } = require('child_process'); exec('brightness') ``` 以上代码中,我们使用Node.js的`exec`函数来执行`brightness`命令。`brightness`是Mac电脑上的一个命令行工具,可以获取或设置屏幕亮度。 接下来,我们可以通过处理`exec`函数的回调来获取输出结果。代码如下: ```javascript exec('brightness', (error, stdout) => { if (error) { console.error(`执行命令出错: ${error.message}`); return; } console.log(`屏幕亮度: ${stdout}`); }); ``` 以上代码通过回调函数处理命令执行结果,如果有错误则打印错误信息,否则打印屏幕亮度。 完整代码如下: ```javascript const { exec } = require('child_process'); exec('brightness', (error, stdout) => { if (error) { console.error(`执行命令出错: ${error.message}`); return; } console.log(`屏幕亮度: ${stdout}`); }); ``` 要注意的是,以上代码只能获取当前屏幕亮度,如果想要设置屏幕亮度,可以使用`brightness`命令加上参数进行设置。 ### 回答3: 使用原生node.js获取mac电脑屏幕亮度的方法是通过调用系统命令来实现。 首先,我们需要使用child_process模块来执行shell命令。然后,我们可以使用以下命令获取屏幕亮度: ``` const { exec } = require("child_process"); exec("pmset -g | grep 'display' | awk '{print $2}'", (error, stdout, stderr) => { if (error) { console.log(`exec error: ${error}`); return; } if (stderr) { console.log(`stderr: ${stderr}`); return; } const brightness = stdout.trim(); console.log(`屏幕亮度为: ${brightness}`); }); ``` 在上述代码中,我们使用了pmset命令来获取电源管理设置,然后使用grep和awk来提取屏幕亮度值。最后,我们将获取到的屏幕亮度值输出到控制台。 请注意,上述方法适用于macOS系统,请确保您的电脑上已经安装了Node.js环境,并在运行代码之前安装了所需的依赖库。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员-石头山

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值