node文件上传(服务器与数据库)与下载

  • 我们之前已经学习过使用multer插件来进行文件的拆分上传到服务器上,现在我我们就来学习一下multer插件的另一种模式 — 上传文件到数据库以及如何从数据库中将该文件下载下来
  • multer中间件的在官方文档

使用multer将数据上传到数据库

  1. 首先我们需要一个数据库,这里使用的是MySQL,我们建一张表,表中的字段设置如下:

    • 连接数据库
    /* ------------------------- 数据库操作 ------------------------------ */
    const mysql = require('mysql2') //引入mysql2模块用于操作数据库
    const db = mysql.createPool({ //创建连接池
    host: '127.0.0.1', // 数据库域名
    user: 'root', // 数据库账号
    password: 'Zpl13189417387', // 数据库密码
    database: 'node_upload_download1' // 连接的数据库名称
    })

    module.exports = db // 将连接池暴露出去

image

  1. 随后在node中下载引入multer

    1. 下载multer

      npm i multer
    2. 在文件中引入

    const multer  = require('multer')//引入multer中间件处理文件

    const storage = multer.memoryStorage()// 使用磁盘存储模式将数据存储到数据库中
    const upload = multer({ storage: storage,fileFilter})

    // 设置一个方法用于对前端页面上传文件的文件名作中文转码
    function fileFilter(req, file, callback) {
    // 解决中文名乱码的问题
    file.originalname = Buffer.from(file.originalname, "latin1").toString(
    "utf8"
    );
    callback(null, true);
    }
    1. 在前端设置form表单上传
    <body>
    <input type="file" id="file" /><br><br>
    <div>
    当前数据库中的文件数据为:
    <table border="2">
    <tbody></tbody>
    </table>
    </div>
    <!-- 引入axios用于发送网络请求 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.1/axios.min.js"></script>
    <script>
    // 文件上传
    const file = document.getElementById('file')
    axios.defaults.baseURL = 'http://127.0.0.1:3000'//设置请求url本体

    file.addEventListener('change', async (e) => {//监听上传事件
    const formDate = new FormData()
    formDate.append('file', e.target.files[0])//在form表单中添加一个字段file同时将该字段赋予上传的文件数据
    const { data: res } = await axios.post('/file', formDate, {
    // 因为axios默认的Content-Type 为json,所以需要修改
    headers: {
    'Content-Type': 'multipart/form-data'
    }
    })
    console.log(res)
    })

    // 文件展示以及下载:
    // 获取展示列表
    var tbody = document.querySelector('tbody')

    // 页面一上来就获取数据库中的用户信息
    axios.get('/getFileData').then(res=>{
    var data = res.data.data
    console.log('数据库中的所有文件数据:',res.data.data);

    // 将获取到的数据填入到tbody中
    tbody.innerHTML = data.map(item=>`
    <tr>
    <td>${item.id}</td>
    <td>${item.name}</td>
    <td><a href="http://127.0.0.1:3000/dw?id=${item.id}" download=""">下载</a></td>
    </tr>
    `).join('')
    })
    </script>
    1. 后端设置接口联通数据库,将前端发送过来的文件数据(buffer二进制数据)保存到数据库中
    // 文件上传(单文件)
    //upload.single():将前端页面传过来的文件数据入到multer中并以数据流的方式保存到数据库中
    app.post('/file', upload.single('file'), (req, res) => {
    const file = req.file
    const data = {
    name: file.originalname,//文件名
    value: file.buffer
    }
    const sql = 'INSERT INTO file SET ?'
    db.query(sql, data, (err, results) => {
    if (err) {
    return res.send(err.message)
    } else {
    res.send('ok')
    }
    })
    })

结果展示:

image

文件下载

  • 数据库中的blob文件流数据如何通过网页下载到本地,就需要先了解网页上的文件是如何下载到本地的.

  • 文件下载是前端开发中一个常见的功能,目前主流的下载实现主要有两种:静态文件直接下载文件流下载

    • 静态文件下载: 这就是我们之前使用multer将文件数据传到服务器上,我们可以通过浏览器的文件策略直接下载服务器上的静态资源文件。例如我们在网页上通过点击a标签打开的excel,word等文件,浏览器会自动的帮我们下载这些东西。
    • 文件流下载: 文件流下载即通过请求后端接口,接口返回blob类型的数据,前端组装成文件后自行下载即可。一般用于数据文件的导出。 这就是我们冲数据库中下载文件的方式
  • 现在的web页面文件下载的方式有多种:

    1. 标准URL下载方式:
      • 这种方法可以通过在web页面中使用a标签herf属性来指向文件下载的 url来下载文件,并且配置a标签download属性来使页面不发生跳转下载文件,但是这种方法存在缺陷:暴露了文件的网站路径动态的灵活性不够
    2. 通过服务器端脚本向浏览器方输出二进制流的方式下载(主要使用这种方式来下载):
      • 这种方法通过在服务器端就设置好发送下载请求时返回的数据形式以及保存方式(下载到本地还是展示在页面上),我们可以通过res.set这个配置项(仅在express框架中适用)来配置服务器端响应给前端数据的方式以及格式(http响应头),
    // express内配置http响应头数据返回给前端
    res.set({
    'Content-Type': 'application/x-7z-compressed',// 响应头中返回的数据类型
    'Accept-Ranges': 'bytes', // 接收的数据格式(buffer二进制数据设置为字节模式)
    // 返回的文件约束一般有两种值:attachment表示文件下载 inline表示文件展示在页面上
    'Content-Disposition': `attachment; filename="${results[0].name}"`
    })

后端接口的代码展示

// 文件下载
app.get('/dw', (req, res) => {
const id = Number(req.query.id)//前端传过来对应的文件id
const sql = `SELECT * FROM file where id=${id}`//查询数据库
db.query(sql, (err, results) => {
if (err) { //错误判断
return res.send(err.message)
}
res.set({ // 设置数据返回的http响应头
'Content-Type': 'application/x-7z-compressed', //返回数据格式为7z压缩包
'Accept-Ranges': 'bytes', // 数据接收范围为字节型
'Content-Disposition': `attachment; filename="${results[0].name}"`//下载模式
})
res.send(results[0].value)//设置好响应头后向数据相应回前端
})
})

结果展示:

image

完整的代码展示

  1. 前端页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件的上传与下载</title>
</head>
<body>
<input type="file" id="file" /><br><br>
<div>
当前数据库中的文件数据为:
<table border="2">
<tbody></tbody>
</table>
</div>
<!-- 引入axios用于发送网络请求 -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.1/axios.min.js"></script>
<script>
// 文件上传
const file = document.getElementById('file')
axios.defaults.baseURL = 'http://127.0.0.1:3000'//设置请求url本体

file.addEventListener('change', async (e) => {//监听上传事件
const formDate = new FormData()
formDate.append('file', e.target.files[0])//在form表单中添加一个字段file同时将该字段赋予上传的文件数据
const { data: res } = await axios.post('/file', formDate, {
// 因为axios默认的Content-Type 为json,所以需要修改
headers: {
'Content-Type': 'multipart/form-data'
}
})
console.log(res)
})

// 文件展示以及下载:
// 获取展示列表
var tbody = document.querySelector('tbody')

// 页面一上来就获取数据库中的用户信息
axios.get('/getFileData').then(res=>{
var data = res.data.data
console.log('数据库中的所有文件数据:',res.data.data);

// 将获取到的数据填入到tbody中
tbody.innerHTML = data.map(item=>`
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td><a href="http://127.0.0.1:3000/dw?id=${item.id}" download=""">下载</a></td>
</tr>
`).join('')
})
</script>
</body>
</html>

  1. 后端服务器
/* ---------------------- 文件上传1 ------------------------------*/
// 引入必要的模块
const express = require('express')
// 创建服务器
const app = express();
const db = require('./db/index.js')//引入数据库连接
const multer = require('multer')//引入multer中间件处理文件
// const upload = multer({ dest: 'public/uploads/' })//自动创建处理后的文件保存目录,同时可也可以指定拓展名(保存到服务器)
const storage = multer.memoryStorage()// 使用内存存储模式将数据存储到数据库中
const upload = multer({ storage: storage,fileFilter})

// 设置一个方法用于对前端页面上传文件的文件名作中文转码
function fileFilter(req, file, callback) {
// 解决中文名乱码的问题
file.originalname = Buffer.from(file.originalname, "latin1").toString(
"utf8"
);
callback(null, true);
}

app.all("*", function (req, res, next) {
//设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin", "*");
//允许的header类型
res.header("Access-Control-Allow-Headers", "*");
//跨域允许的请求方式
res.header("Access-Control-Allow-Methods", "*");
if (req.method.toLowerCase() == 'options')
res.send(200); //让options尝试请求快速结束
else
next();
});

// 配置解析post参数的两个内置中间件
// 通过express.json()这个中间件,解析表单中的JSON格式的数据
app.use(express.json())//解析post的请求题参数(json格式)
// 通过express.urlencoded()这个中间件,来解析表单中的url-encoded格式的数据
app.use(express.urlencoded({extended:false}))//解析post的请求题参数(encoded格式)

//使用express.static()中间件来托管静态资源
app.use(express.static('./public'))

// 文件上传
//upload.single():将前端页面传过来的文件数据入到multer中并以数据流的方式保存到数据库中
app.post('/file', upload.single('file'), (req, res) => {
const file = req.file
const data = {
name: file.originalname,//文件名
value: file.buffer
}
const sql = 'INSERT INTO file SET ?'
db.query(sql, data, (err, results) => {
if (err) {
return res.send(err.message)
} else {
res.send('ok')
}
})
})

// 文件获取
app.get('/getFileData',async (req,res)=>{
// 获取数据库中所有的文件信息(文件id,文件名,文件的二进制数据)
const sql = 'SELECT * FROM file'
db.query(sql, (err, results) => {
if (err) {
return res.send(err.message)
}
console.log('数据库中的所有文件数据',results);
res.send({
msg:'获取文件数据成功!',
data:results//返回数据库中的数据
})
})
})


// 文件下载
app.get('/dw', (req, res) => {
console.log(req.query.id);
const id = Number(req.query.id)
const sql = `SELECT * FROM file where id=${id}`
db.query(sql, (err, results) => {
if (err) {
return res.send(err.message)
}
res.set({
'Content-Type': 'application/x-7z-compressed',
'Accept-Ranges': 'bytes',
'Content-Disposition': `attachment; filename="${results[0].name}"`
})
console.log('数据:',results[0].value);
res.send(results[0].value)
})
})

// 启动服务器
app.listen(3000,()=>{
console.log('服务器已启动! 端口3000正在监听...');
})