上传图片到服务器是一个很常见的需求, 应该实现起来很简单. 但是之前没这方面经验, 折腾了一番.
调研
数据库中应该只保存图片的链接, 图片保存在文件系统中, 可以是服务器本地, 可以是自己部署的独立图片服务器, 也可以是第三方服务器.
MongoDB中, BSON文件的上显示16MB. 所以如果图片不超过这个上限, 就可以存BSON; 否则要用GridFS.
对我来说, 最理想的当然是使用第三方图片服务器了. 但是现在并不想花这个钱(囧rz). 所以想先试试存在自己的服务器本地.
界面怎么写?
上传文件有个专门的控件, input[type="file"]
. 浏览了几个网站的实现, 都是把input[type="file"]
隐藏起来(因为它太丑), 然后在相同的区域摆上一个按钮之类的东西(好看一些), 用户点了按钮其实点击了input[type="file"]
.
示例如下:
<label class="btn btn-primary file-chooser">
Change Picture
<input type="file" accept=".jpg, .jpeg, .png" @change="uploadAvatar">
</label>
input[type="file"]
支持属性accept
, 上面的代码只接受.jpg, .jpeg, .png
文件.
@change="uploadAvatar"
是vue中监听input[type="file"]
的change
事件. 用户选择的图片会成为e.target.files[0]
.
uploadAvatar(e) {
service.uploadAvatar(e.target.files[0]);
}
请求怎么发?
我用的是axios处理请求. 搜了一下需要用multipart/form-data
的content-type
发送, 具体如下:
// service.js
uploadAvatar(avatar) {
let data = new FormData();
data.append('avatar', avatar);
return axios.post('/upload-avatar', data, {
headers: { 'content-type': 'multipart/form-data' }
});
}
后端怎么接?
我用的是express + mongodb. 其实我本来打算存BSON的, 反正头像不用16MB那么大.
但是搜了一下可以用multer
, 阴差阳错存到本地了.
npm install -S multer
后:
// index.js
const multer = require('multer');
const avatarUpload = multer({ dest: 'public/avatar/' });
app.use(express.static('public'));
第二行创建了文件夹public/avatar
作为存储图片的地点.
第三行是将整个public
文件夹都作为静态资源, 让外部可以访问.
然后写路由:
router.post(
'/upload-avatar',
authenticator,
avatarUpload.single('avatar'),
(req, res) => {
// Set { new: true } to return the updated one, rather than the original one.
User.findByIdAndUpdate(req.user.id, { avatar: req.file.path }, { new: true }).then(user => {
res.json({ message: "ok" });
});
}
);
authenticator
是我在JWT上手: Express+Passport做的用户身份JWT验证逻辑, 此处请忽略. 示例代码里面没删除是因为req.user.id
需要它.
avatarUpload.single('avatar')
引入了multer
的中间件, 读取请求中的key为avatar
的文件, 返回到req.file
中. req.file
是一个object
, 里面有mimetype
等信息, 我们需要的是req.file.path
, 即服务器本地的地址.
User.findByIdAndUpdate
就是把这条信息更新到mongoDB中.
至此, 打完收工.
回头来看, 感觉真简单. 但是调研的时候接触到了很多听都没听过的东西, BSON, GridFS, multer, 充满不确定性 :P.