文件上传功能技术选型和前后端实现
# 文件上传功能技术选型和前后端实现
# 一、起因
上下文:利用vercel托管了一个前端页面what to eat (opens new window) 一个摇菜单的页面
xpy产品加了个需求:给菜单加上图片预览功能
# 二、功能点
- 前端实现图片上传
- node后端接受图片,存档服务器
- 前端回显需要考虑网络带宽问题,需要对上传的图片做压缩和裁切处理
# 三、实现方案
# 1. 直接存服务器上
图片应该不会很多,所以最开始尝试直接将图片存在vercel的node服务端
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
// 引入multer
const multer = require('multer');
const storage = multer.diskStorage({
// 上传图片的存放位置在uploads
destination: function(req, file, cb) {
cb(null, './static/');
},
// windows下使用-代替:
filename: function(req, file, cb) {
cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname)
}
})
const fileFilter = (req, file, cb) => {
//判断上传图片类型
cb(null, true);
// if (file.mimetype === 'image/jpg' || file.mimetype === 'image/png') {
// cb(null, true);
// } else {
// cb(null, false);
// }
}
// limit上传图片尺寸限制
const uploads = multer({
storage: storage,
limit: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter,
// dest: './public/'
});
// 接受一个文件。这个文件的信息保存在 req.file。最终存放在uploads
router.post('/', uploads.single('image'), (req, res, next) => {
console.log(11111)
console.log(req.file)
const file = req.file
if(file.size > 0) {
res.status(200).json({
code:'000000',
data:file.filename
})
}
const product = new Product({
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
price: req.body.price,
productImage: req.file.path
});
product
.save()
.then(result => {
res.status(201).json({
message: "created product successfully",
createdProduct: {
name: result.name,
price: result.price,
_id: result._id,
productImage: result.productImage,
request: {
type: 'GET',
url: 'http://localhost:3000/products/' + result._id
}
}
})
})
})
module.exports = router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
利用multer做简单实现后发现一个问题,vercel对文件访问做了限制
// 开放静态目录
app.use(express.static('static'))
2
vercel部署的网站,用express开放的静态目录无法访问,只能放弃。
但是如果你是自己的服务器,可以考虑这个方案。
# 2. seafile私人云盘
本来觉得这个小网站就不需要整麻烦了,所以想偷懒把图片丢服务器文件夹完事,看来静态资源存储这个活逃不掉了。
然后,想到了我自己搭建的seafile私人云盘,可以作为图片服务器,虽然更麻烦,但是
这样图片静态资源存储和网站服务器解耦,更有利于提高网站扩展性和兼容性
实现步骤大概分为三步:
- 图片上传到seafile私人云盘,上传成功后返回图片URL
- mongoDB数据库存储URL相对路径即可
- 前端展示时:取出URL相对路径+云盘地址即可
有关私人云盘搭建可以访问搭建个人云盘seafile (opens new window)
有关seafile的坑 我踩了很多,甚至看了seafile jdk后解决了一个issue
这里直接说结论:不推荐将seafile作为云存储,原因有:官方js库很久都无人维护,官方文档接口调用描述与实际不符合等等问题。
但是还是推荐seafile作为私人云盘,虽然API做的很差,但是云盘很稳定, pc端移动端和web页面做的也很好。
# 3. 终极方案:七牛云
最终做了很多功课后,我选择了云服务商七牛云
- 全免费性能不缩水的10GB对象存储,完全够用了
- Github JDK star 1.3k 最近持续更新 issues也都有官方解决
- 官方文档一看就懂了,clone下来开箱即用
账号注册和安装引入过程不赘述了,给两个官方链接
七牛 github js-sdk (opens new window)
服务端代码
/**
* 获取七牛token
*/
const path = require('path')
const express = require('express')
const router = express.Router()
const qiniu = require('qiniu')
const AK = 'xxx' //在个人中心内
const SK = 'xxx'
const bucket = 'forespe' //空间名称
//鉴权对象mac
const mac = new qiniu.auth.digest.Mac(AK, SK)
//获取上传的token
const options = {
scope: bucket,
expires: 3600 * 24 //到期时间
}
const putPolicy = new qiniu.rs.PutPolicy(options)
const uploadToken = putPolicy.uploadToken(mac)
router.get('/qiniu-info', (req, res) => {
res.status(200).json({
token: uploadToken
})
})
module.exports = router
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
前端代码:
// 上传方法
import * as qiniu from 'qiniu-js'
import moment from 'moment'
import { client } from './index'
export const getQiniuToken = () => client.get('/system/qiniu')
export const uploadFile = function (file, token) {
const fileName = moment().format('YYYYMMDDTHHmmss-') + file.name
const putExtra: any = {
fname: file.name, // 文件原文件名
params: {}, // 用来放置自定义变量
mimeType: null // 用来限制上传文件类型,为 null 时表示不对文件类型限制;eg: ["image/png", "image/jpeg"]
}
const config = {
useCdnDomain: true, // cdn加速
region: qiniu.region.z0 // 区域
}
const observable = qiniu.upload(file, fileName, token, putExtra, config)
// 上传开始
return observable
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用
_uploadFile(file) {
this.uploadImgLoading = true
const that = this
uploadFile(file, this.qiniuToken).subscribe({
next(res) {
// 进度
console.log('next', res)
},
error(err) {
console.log('err', err)
},
complete(res) {
// 来到这里就是上传成功了。。
that.$message.success('上传成功')
that.uploadImgLoading = false
that.uploadImageUrl = res.key
console.log('complete', res)
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
回掉函数 next() 里有上传进度信息,上传大文件时很人性化
mongoDB数据库直接存储的文件名
前端回显为: 图片CDN url + 图片文件名
以后如果要迁移项目或者换地方存储,只需要转移图片,不需要修改数据库的值。
完
需求是研发的第一推动力,感谢xpy