├─ bin/www // 主程序入口
├─ dbConnect // mysql 连接数据库
├─ routes // 路由文件
│ ├─ download.js // 下载文件
│ ├─ upload.js // 上传文件
│ ├─ query.js // 查询已上传文件列表
│ └─ delete.js // 通过ID删除文件
├─ static // 静态资源 用于存放上传的文件
└─ app.js // express核心文件
multer
用于文件上传 只能用于接收formdata形式上传的文件mysql
连接数据库 绑定新旧文件名 API中会使用到uuid
用于生成UUID来重命名文件 防止文件名重复导致的覆盖或错误的产生npm install express -g # 全局安装express
npm install express-generator -g # 全局安装express生成器
express uploadTestDemo # 初始化项目
cd uploadTestDemo
yarn install # 安装express需要的依赖文件
yarn add multer mysql uuid # 安装项目需要的依赖文件
博文的代码并不是全部代码,我会在最下方放上github地址,功能的测试都是用postman
完成的
文件routes/upload.js
,上传文件接口
var multer = require('multer');
var uuid = require('uuid/v1');
// 定制上传控件
var storage = multer.diskStorage({
destination: function (req, file, cb) {
// 保存的路径,备注:需要手动创建,如果目录不存在 上传时会报错
cb(null, 'static/')
},
filename: function (req, file, cb) {
let fileFormat = (file.originalname).split('.') // 取后缀
// 设置保存时的文件名,uuid + 后缀
cb(null, uuid() + '.' + fileFormat[fileFormat.length - 1])
}
})
var upload = multer({
storage,
})
router.post('/', upload.single('file'), function (req, res, next) {
// 上传完成后返回文件名
res.json({
code: 0,
data: {
fliename: req.file.filename
},
msg: 'success'
})
res.end()
})
创建文件dbConnect/index.js
,用来进行MySQL的配置及连接。
var mysql = require('mysql')
// 创建连接
let connection = mysql.createConnection({
host: '127.0.0.1', // 主机名
user: 'root', // 用户名
password: 'root', // 密码
database: 'testDatabase' // 要连接数据库
})
// 执行创建连接
connection.connect();
module.exports = connection
修改刚刚的upload.js
文件,加入对上传文件信息的保存功能
var sqlConnect = require('../dbconnect/index');
/** 新增数据SQL语句
* @params { String } staticname 静态资源名
* @params { String } filename 原文件名称
* @params { String } mime 上传文件的mime类型
* @params { Number } size 上传文件的大小(kb)
*/
var addSql = 'INSERT INTO uploadfiles(staticname, filename, mime, size) VALUES(?, ?, ?, ?)';
router.post('/', upload.single('file'), function (req, res, next) {
var addSqlParams = [ // 设置新增字段数据 对应addSql中的四个参数
req.file.filename,
req.file.originalname,
req.file.mimetype,
req.file.size
];
// 执行数据库方法
sqlConnect.query(addSql, addSqlParams, (err, result) => {
if (!err) {
res.json({
code: 0,
data: {
fliename: req.file.filename
},
msg: 'success'
})
} else {
res.json({
code: 0,
data: {},
msg: `error ${err.message}`
})
}
res.end()
})
})
在测试前需要去数据库创建这个数据库和对应的表结构
CREATE TABLE `uploadfiles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`staticname` varchar(100) NOT NULL COMMENT '静态资源地址',
`filename` varchar(100) DEFAULT NULL COMMENT '文件名',
`mime` varchar(100) DEFAULT NULL COMMENT '上传文件mime类型',
`size` double DEFAULT NULL COMMENT '文件大小 kb',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
然后在postman
中将请求配置为下图,发送后得到的返回数据为保存在后端static
目录中的文件名
可以看到通过post请求后端成功返回了文件名。从下图看到文件已经存到对应的文件夹下,并将文件信息保存到数据库中了。
routes/upload.js
router.get('/', function (req, res, next) {
if (!req.query.filename) { // 确定需要的参数存在
res.status(400).json({
code: 1,
data: '',
msg: 'The file name field of the query failed!'
})
return res.end()
}
let filename = req.query.filename
res.download('./static/' + filename)
})
验证所需参数的部分其实可以封装起来 写一个validField工具类,但是因为时间原因 没有封装 后期进行优化的时候可以改。
通过请求上面的接口在浏览器中会出现下载提示,如下:
修改download.js
文件
var sqlConnect = require('../dbconnect/index');
// 通过staticname查询的SQL
var querySql = 'SELECT * FROM uploadfiles WHERE staticname = ?'
router.get('/', function (req, res, next) {
if (!req.query.filename) {
res.status(400).json({
code: 1,
data: '',
msg: 'The file name field of the query failed!'
})
return res.end()
}
let filename = req.query.filename
sqlConnect.query(querySql, [filename], (err, result)=>{
if(err || result.length===0) { // 如果查询报错或是是未找到文件的时候返回错误信息
res.json({
code: 0,
data: false,
msg: `downloadFileError ${err || 'not found file!'}`
})
res.end()
} else {
res.download(`./static/${filename}`, result[0].filename)
}
})
})
谷歌浏览器不好测试 所以使用了Firefox进行,请求之后的结果如下,可以发现浏览器下载器 成功更改了下载时的文件名称。
routes/query.js
因为现在只有通过上传时返回的数据和数据库才能知道已经上传的文件,不是很方便 所以单独做一条用于展示已上传文件列表的接口,方便展示。
var express = require('express');
var sqlConnect = require('../dbconnect/index');
var router = express.Router();
// 查询SQL语句
var querySql = 'SELECT * FROM uploadfiles'
/* GET query listing. */
router.get('/', function(req, res, next) {
sqlConnect.query(querySql, function(err, result){
if (!err) {
//将结果以json形式返回到前台
res.json({
code: 0,
data: result,
msg: 'success'
});
} else {
res.json({
code: 1,
data: [],
msg: 'query database error'
})
}
res.end()
})
});
module.exports = router;
删除功能:既要删除数据库中的数据同时删除本地文件。
分步骤
1.查询数据库 判断对应ID的数据是否存在
2.删除数据库中的上传记录
3.删除本地文件
var sqlConnect = require('../dbconnect/index');
var fs = require('fs'); // nodejs fileSystem
var deleteSql = 'DELETE FROM uploadfiles WHERE id = ?' // 通过ID删除
var queryOneSql = 'SELECT * FROM uploadfiles WHERE id = ?' // 通过ID查询
router.delete('/:id', function (req, res, next) {
let id = req.params.id
new Promise((resolve, reject) => { // 查询记录
sqlConnect.query(queryOneSql, [id], (err, result) => {
if (err || result.length === 0) {
reject(err)
} else {
let dataObj = result[0]
resolve(dataObj)
}
})
}).then(result => { // 删除记录
return new Promise((resolve, reject) => {
sqlConnect.query(deleteSql, [id], (err, res) => {
if (!err) {
resolve(result)
} else {
reject(err)
}
})
})
}).then(result => { // 删除磁盘文件
fs.unlinkSync(`./static/${result.staticname}`)
res.json({
code: 0,
data: false,
msg: `delete id ${result.id} success`
})
}).catch(err=>{ // 异常响应
res.json({
code: 1,
data: [],
msg: `error: ${err}`
})
}).finally(()=>{ // 结束请求
res.end()
})
})
其实仔细想想,应该不用在删除前进行一次查询操作,直接删除可以减少步骤。在后面的维护再优化吧。
最后贴上项目的github地址,如果有什么不足之处也可以通过github联系我
https://github.com/766aya/fileUploadDemo
总结一下,这个上传功能只是简单的实现了一下,并没有深究其中的原理。在项目中很多的请求和SQL操作其实可以进行二次封装从而简化代码量,增加代码的可阅读性。