用(Node+MongoDB)搭建最简单的图床或者网盘__Vue.js
发布于 4 年前 作者 banyungong 1416 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

1 文章起源

在掘金白嫖了这么久,总觉得得有个产出,否则白嫖的怪不好意思的🤣看到网上各种各样的附件上传,觉得对于入门的人可能有些难度,今天就带来了关于Node+Mongodb的附件上传下载方面的文章。该项目比较简单,所以目录简单的一批,各位学到的可以快速搭建一个小网盘图床啥的。感谢掘金各位大大的文章让我升职加薪了😁(俺是男娃)

2 起手式

2.1 概念

首先我们得先去了解一下👉Mongodb的文件储存(GridFS)是啥,因为我们都是基于 GridFS 来进行文件储存

2.2 我们需要什么

了解了大概概念后就可以着手安装我们必须的插件了

  • 👉express (这是啥不用我多说)
  • 👉body-parser (Node解析body的中间件)
  • 👉ejs (模板引擎,快速开发就不搞前后端分离了,有兴趣的小伙伴可以用Vue/React来搭建小网盘)
  • 👉gridfs-stream (轻松地与MongoDB GridFS之间传输文件。)
  • 👉method-override (我们用form表单简单上传,因为form表单不支持put/delete请求方式,所以把它安排上了,小伙伴可自行使用Ajax,就不需要这么麻烦了)
  • 👉mongoose (用于连接mongodb必不可少的插件)
  • 👉multer (Multer是用于处理多部分/表单数据的node.js中间件,主要用于上传文件。 它被编写在busboy之上,以实现最大效率。)
  • 👉multer-gridfs-storage (Multer的GridFS存储引擎可将上传的文件直接存储到MongoDb。)
  • 👉nodemon (热更新)

以上就是我们需要准备的东西了

// 懒人复制区域

npm install express body-parser ejs gridfs-stream method-override mongoose multer multer-gridfs-storage
// or
yarn add express body-parser ejs gridfs-stream method-override mongoose multer multer-gridfs-storage

2.3 初始化一个项目

// 可自行补充信息
// npm init

然后在根目录新建一个入口文件 app.js,和页面 views/index.ejs

3 现在项目开始了

3.1 先将基础部分完事

将我们安装的包引入,再跑跑看看

const express = require('express')
const path = require('path')
const crypto = require('crypto')
const mongoose = require('mongoose')
const multer = require('multer')
const GridFsStorage = require('multer-gridfs-storage')
const GridFsStream = require('gridfs-stream')
const methodOverride = require('method-override')
const bodyParser = require('body-parser')

const app = express()

app.set('view engine''ejs') // 设置模板引擎

app.use(bodyParser.json()) 
app.use(methodOverride('_method'))

app.get('/', (req, res) => {
        res.render('index')
    })
})

const port = 5000
app.listen(port, () => {
    console.log(`App listering on port ${port}`)
})

一般来说启动了app.js的话我们在浏览器访问 http://localhost:5000 就能看到 views/index.ejs 中的界面了,如果没有,自行查看控制台是否报错

3.2 连接我们的Mongodb数据库

我这边用的本地mongodb数据库,线上也是一样的,我们可以用 NoSQL manager for mongdb 来查看我们数据库里面的数据,我们新建一个新的集合,我这边叫 grid_uploads。所以连接的话也是连接这个集合

// 数据库的链接
const mongoURL = 'mongodb://localhost:27017/grid_uploads'

const connect = mongoose.createConnection(mongoURL, {
    useNewUrlParser: true,
    useUnifiedTopology: true
})

可以尝试在NoSQL写入一些数据,具体使用可以参考博客👉【MongoDB】NoSQL Manager for MongoDB 教程

3.3 美化一下界面(views/index.ejs)

作为一个小两年的前端工程师,已经练就了像素眼了,我们肯定不能把界面做的辣么丑对吧,眼睛过不去啊,所以我们简单的用bootstrap4来做个界面好了

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <style>
        img {
            width: 100%;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-6 m-auto">
                <h2 class="text-center display-4 my-4">Mongo文件上传</h2>
                <form action="/upload" method="POST" enctype="multipart/form-data">
                    <div class="custom-file mb-3">
                        <input type="file" name="file" id="file" class="custom-file-input">
                        <label for="file" class="custom-file-label">选择文件</label>
                    </div>
                    <input class="btn btn-primary btn-block" type="submit" value="提交">
                </form>
                <hr>
            </div>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

</html>

那么我们请求 http://localhost:5000 的话我们看到的应该是这样子的

3.4 做一些必须的处理

// 定义gfs变量,后续我们进行数据库文件操作的时候可不能少
let gfs;
connect.once('open', () => {
    // 监听数据库开启,通过 gridfs-stream 中间件和数据库进行文件的出入控制
    gfs = GridFsStream(connect.db, mongoose.mongo)
    gfs.collection('upload')
    // 它会在我们数据库中建立 upload.files(记录文件信息)  upload.chunks(存储文件块)
})

// 使用 multer-gridfs-storage Multer 中间件来讲我们上传的附件直接存储到MongoDb
const storage = new GridFsStorage({
    url: mongoURL,
    file: (req, file) => {
        return new Promise((resolve, reject) => {
            // 下面注释部分是给文件进行重命名的,如果想要原文件名称可以自行使用 file.originalname 返回,
            // 建议有时间的小伙伴存储两个文档,一个记录原文件名,一个记录加密文件名,然后返回到页面的时候可以将中文名返回去
            
            // crypto.randomBytes(16, (err, buf) => {
            //     if (err) {
            //         return reject(err)
            //     }
            //     const filename = buf.toString('hex') + path.extname(file.originalname)
            //     const fileinfo = {
            //         filename,
            //         bucketName: 'upload'
            //     }
            //     resolve(fileinfo)
            // })
            const fileinfo = {
                filename: new Date() + '-' + file.originalname,
                bucketName: 'upload'
            }
            resolve(fileinfo)
        })
    }
})

const upload = multer({ storage })

3.5 写我们上传第一个文件的接口

app.post('/upload', upload.single('file'), (req, res) => {
    res.redirect('/')
})

看起来简简单单,请记着这么几件事

  • 在views/index.ejs中 (input type=file 指定的name得和接口的 upload.single('file') 一样
  • 上传完文件我们重定向回我们的首页 此时我们就可以在NoSql看到我们的两个文档有数据了

这是upload.chunks

这是upload.files

3.6 获取我们所有的文件信息

获取我们所有的文件

app.get('/files', (req, res) => {
    // 通过查找返回一个数组对象回去
    gfs.files.find().toArray((err, files) => {
        if (!files || files.length === 0) {
            return res.status(404).json({
                err: '文件不存在!'
            })
        }
        return res.json(files)
    })
})

我们可以进行一些美化操作,比如我们可以将上传是图片的,返回到界面的话以图片显示,其他则以 a 标签的格式显示(可点击下载),所以我们可以将 views/index.ejs的界面进行美化改造(ejs语法用起来确实蛮麻烦的),进行重新排版以及添加删除按钮

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css"
        integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <style>
        img {
            width: 100%;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-6 m-auto">
                <h2 class="text-center display-4 my-4">Mongo文件上传</h2>
                <form action="/upload" method="POST" enctype="multipart/form-data">
                    <div class="custom-file mb-3">
                        <input type="file" name="file" id="file" class="custom-file-input">
                        <label for="file" class="custom-file-label">选择文件</label>
                    </div>
                    <input class="btn btn-primary btn-block" type="submit" value="提交">
                </form>
                <hr>
            </div>
        </div>
        <div class="row">
            <% if(files){ %>
            <% files.forEach(function(file){ %>
            <div class="col-sm card card-body m-3  col-md-2">
                <% if(file.isImage){ %>
                <img src="image/<%= file.filename %>" />
                <% } else { %>
                <a href="download/<%= file.filename %>"><%= file.filename %></a>
                <%}%>
                    <form action="/files/<%= file._id%>?_method=DELETE" method="POST">
                <button class="btn btn-danger btn-block mt-4">删除</button>
                </form>
            </div>
            <% }) %>
            <% }else { %>
            <p class="card card-body text-center display-4 my-4">文件不存在</p>
            <% } %>
        </div>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
    crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
    crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.min.js"
    integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
    crossorigin="anonymous"></script>

</html>

要将 ejs 中的files变量获取到我们应该重写一下 get('/')接口,使其在访问localhost:5000的时候先去读取一下数据库文件信息并输出到页面中去

app.get('/', (req, res) => {
    gfs.files.find().toArray((err, files) => {
        if (!files || files.length === 0) {
            res.render('index', { files: false })
            return
        }
        files.map(file => {
            // 如果是以下图片类型我们就在前端展示出来,其余一律按附件处理,通过 isImage 来区分图片和非图片
            const imageType = ['image/png''image/jpg''image/gif''image/jpeg']
            if (imageType.includes(file.contentType)) {
                file.isImage = true
            } else {
                file.isImage = false
            }
        })
        res.render('index', { files: files })
    })
})

完成上述的情况我们访问首页的话就行该是如下情况

3.7 单个文件下载

在这里我们通过a标签访问 /download/:filename 接口,filename是文件名,当然可以用其他的比如_id,当查找到有该附件的时候就将它合并成可读留,通过管道返回,这样在前端界面上点击文件标题就可以直接下载了

app.get('/download/:filename', (req, res) => {
    gfs.files.findOne({ filename: req.params.filename }, (err, file) => {
        if (!file) {
            return res.status(404).json({
                err: '文件不存在!'
            })
        }
        const readstream = gfs.createReadStream(file.filename)
        readstream.pipe(res)
    })
})

3.8 单个文件删除

在这里我们通过a标签访问 /files/:id 接口,id对应,点击删除按钮,就直接删除了,并重定向到首页

app.delete('/files/:id', (req, res) => {
    gfs.remove({ _id: req.params.id, root: 'upload' }, (err) => {
        if (err) {
            return res.status(404).json({
                err: '删除的文件不存在!'
            })
        }
        res.redirect('/')
    })
})

由于我们一直用form做请求,但是form表单没有delete请求方式,所以我们用到了method-override插件,当然要是用Ajax就没关系了,我们项目毕竟速成嘛,主要看效果和过程

4 完结撒花了

简简单单的告一段落了,学了的小伙伴们可以尝试更加深入的操作,所以我们就可以用此项目来做一个图床或者小网盘盘,还是简简单单滴。当然该附件上传也有一定的限制问题,比如大文件可能上传时间更久,我们就需要采用文件分片方式上传了,可以看掘金的👉文件分片的案例。

最后感谢各位小伙伴的品尝,如有新的好玩的东西,我再来分享一下

读一本好书,就是在和高尚的人谈话。 ——歌德(我真说过)

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 柠檬💫不酸 原文链接:https://juejin.im/post/6858866879339954189

回到顶部