[Nodejs][mongodb][图床]前端上传的图片后端保存在数据库中

需求

一般情况下,上传的图片(文件)都放文件夹内,获取的时候通过 主机地址+文件路径便可获取

但是现在我不想把图片存储在服务器文件夹内,我想把前端代码、后端代码、数据分离出来

这样不管换部署环境还是管理,它们都有自己的空间,耦合性非常低

实现

环境

  • 前端:JavaScript
    
  • 后端:Nodejs(express)
    
  • 数据库:MongoDB
    

数据库使用了NoSQL类型的MongoDB,因为它几乎可以保存任何东西,并且以BSON类型存储,它类似与JSON
这样前端、后端、甚至数据库都可以用一种标准化的数据结构。
MongDB对文档(记录)没有严格的字段限制,也就是说,你可以在一张集合(表)里存储任何东西。
比如,下图中,第一条文档是一个json对象,里边包含了id、md5、index、data、size这些字段,但你完全可以在下一条文档中只有id、name、age等,每条文档可以不用包含一组固定的字段。但对于一般信息,非常不建议这样做。
在这里插入图片描述
也得益于这个数据库支持存储二进制数据,才使得它可以存储任何东西。
但是一个文档最大只支持16M空间,要想存储超大文件,可以使用官方自带的模块GridFS,或者把文件分段存储。

前端上传部分

上传图片主要用到了 Form-Data 类型的请求头

<body>
  <form>
    <p>file: <input name="file" type="file" id="fileInput"></p>
    <p><button type="button" id="submit">提交</button></p>
  </form>
</body>

<script>
  const formData = new FormData()
    
  fileInput.addEventListener('change', (e) => {
    const file = fileInput.files[0]
    formData.append(file.name, file)
  })
  submit.addEventListener('click', () => {
    const xhr = new XMLHttpRequest()
    xhr.open('POST', '/file')
    xhr.send(formData)
  })
</script>

代码是我直接粘过来的
前端主要是把数据放到 FormData中,然后用Ajax发送到服务器

后端接收部分

问题主要在这块,一般的图片或文件接收都有现成的框架,我们并不知道它是如何实现把数据接收并保存的,我找了一篇资料1,这位博主写的很好,详细原理过程可以看下边引用中的链接

  • 请求路由模块
post('/upload', (req, res) => {
	// 最大尺寸
    const maxsize = 15000000
    // 请求头分隔符
    const separtor = `--${req.headers['content-type'].split('boundary=')[1]}`
    // 获取一块 Buffer 空间
    let data = Buffer.alloc(0)
    let isMax = false
    req.on('data', (chunk) => {
      // 把每次上传的数据包拼接到 data
      data = Buffer.concat([data, chunk])
      // 判断尺寸
      if (data.length >= maxsize) {
        res.status(400).json({ result: false })
        isMax = true
        return
      }
    })
    if (isMax) return
    // 上传完成操作
    req.on('end', async () => {
      // 这里用到了解析请求头的函数,具体实现看下一个代码片段
      data = modules.parseFormData(data, separtor)
      // 选择集合(表),集合名为 pic
      // 其中 db 为连接数据库后的实体,不在这里累赘
	  let picc = db.collection('pic')
	  // 这里的 data 实际上是一个数组,包含请求头中的所有数据块
	  // 如果我们只传图片的话,解析请求头后,data 就只有一个元素
	  // 类似与
	  // data {
	  // 	name: ...
	  // 	filename: ...
	  //	body: 二进制 Buffer (如果是图片的话就是二进制Buffer,否则是普通字符串)
	  // }
	  // 此时 data 里包含两个元素,一个数据体信息对象,一个数据体
	  // 具体请移步至下方引用中的链接
	  // 这里插入时多加了一条 body 的 md5,用来索引
	  let result = await picc.insertOne({data: data, md5: md5(data.body)})
	  res.json(result)
      res.end()
    })
  })
  • 解析请求头模块
{
  parseFormData: function (data, separator) {
    if (!Buffer.isBuffer(data))
      return false

    let dataArr = []
    const bufArr = this.split(data, separator).slice(1, -1)

    for (let d of bufArr) {
      let headerVal = {}
      const [head, body] = this.split(d, '\r\n\r\n')
      const headArr = this.split(head, '\r\n').slice(1)
      const [name, value] = headArr[0].toString().split(': ')
      value.split('; ').forEach(item => {
        const [key, val = ''] = item.split('=')
        headerVal[key] = val && JSON.parse(val)
      })
      dataArr.push({ ...headerVal, body })
    }

    return dataArr
  },
  split: function (data, separator) {
    const dataArr = []
    let offset = 0
    let index = data.indexOf(separator, 0)
    while (index != -1) {
      dataArr.push(data.slice(offset, index))
      offset = index + separator.length
      index = data.indexOf(separator, index + separator.length)
    }
    dataArr.push(data.slice(offset))

    return dataArr
  }
}

解析请求头模块内的代码不做解释,可直接使用。它主要是把请求头分解成数组,每一个内容包含信息头对象,以及信息体。

存储过程

在后端接收那块,先把请求头内的内容放在数组内,数组里包含每一条请求对象
每一条请求对象又包含信息,以及信息体
信息体使用Nodejs中的Buffer存储
Buffer是存储二进制数据的
然后打包插入到MongoDB
信息就直接用BSON存储了,Buffer信息体就以二进制存储在BSON内了

获取过程

    getPic: async function (md5) {
      let result
	  // 同样,db是数据库实例
      let picdc = db.collection('pic')
      // 通过 md5 码找到数据
      result = await picdc.findOne({ md5: md5 })
      // 如果找到了,结果应该是这样
      // {
	  //   data {
	  // 	name: ...
	  // 	filename: ...
	  //	body: 二进制 Buffer (如果是图片的话就是二进制Buffer,否则是普通字符串)
	  //   },
	  //   md5: ...
      // }
	  return result ? result : false
    }
  • 这里是发送到客户端
  .get('/:md5', async (req, res) => {
    let picinfo = await mdbctl.getPic(req.params.md5)
    // 需要声明回应头,为 image 类型,png 格式
    res.writeHead(200, { 'Content-Type': 'image/png' })
    // 这里直接发送返回的二进制数据
    res.write(picinfo.data.body)
    res.end()
  })

对于向数据库插入及获取,我只是大概写了一下,用于理解
我的源代码对他们进行了模块化和标准化,牵扯的太多了,就没有贴源代码

思考

  1. 对于直接存放到磁盘,和存放到数据库,他们之间的性能及内存占用不知道孰优孰劣
    直接放到磁盘,服务器发送数据时也应该会把数据放到内存,
    而数据库方式是从数据库获取数据,也是放到内存,然后发送
    应该性能差不多

  1. superYue.文件上传原理解析与实现 [技术/知乎].链接,2022-11-14 ↩︎

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值