npm i -S koa-static
// 先切换到/server
目录下服务端拦截路由请求:html
// 新建文件:server/router/assets.js const Router = require('@koa/router'); const controls = require('../control/assets'); const routerUtils = require('../utils/router'); const { upload } = controls; const router = new Router({ prefix: '/assets' }); const routes = [ { path: '/:category/:id', method: 'POST', handle: upload } ] routerUtils.register.call(router, routes); module.exports = router;
// 新建文件:server/control/assets.js async function upload (ctx, next) { console.log('--------upload=======') } module.exports = { upload, }
koa-body
支持// 更新文件:server/app.js ... const path = require('path'); ... app.use(bodyParser({ multipart: true, //支持文件数据 formidable: { uploadDir: path.resolve(__dirname, './public/temp'), // 图片存储位置 keepExtensions: true } })); ...
multipart: true
支持文件数据,这里以key:value
的形式获取。formidable.uploadDir
文件上传存储位置,若不设置,默认存储到计算机用户目录的缓存位置,最好设置一个可控位置。keepExtensions:true
是否保有后缀名,默认不存。Postman
测试token
Body
--> form-data
--> File
--> Select Files
请求结果:没有任何返回。vs code
的调试控制台反馈:
设置存储文件的路径不存在。前端
存储路径不存在,须要检测文件目录是否存在,若不存在,则新建。git
// 新建文件:server/utils/dir.js const path = require('path'); const fs = require('fs'); function checkDirExist(dirname) { if (fs.existsSync(dirname)) { return true; } else { if (checkDirExist(path.dirname(dirname))) { fs.mkdirSync(dirname); //递归 return true; } } } module.exports = { checkDirExist, }
koa-body
配置// 更新文件: ... const { checkDirExist } = require('./utils/dir'); const fileTempDir = path.resolve(__dirname, './public/temp'); ... app.use(bodyParser({ multipart: true, formidable: { uploadDir: fileTempDir, keepExtensions: true, onFileBegin(key, file) { // 利用钩子函数 checkDirExist(fileTempDir); //file.path= path.resolve(fileTempDir, file.name) // 文件更名 } } }));
koa-body
配置onFileBegin
,能够在处理文件以前,进行一些操做,如:测试目录是否存在、更名、改存储路径等。测试结果:github
fs
模块读写文件那,为何还要第二种方式呢?
利用koa-body
配置onFileBegin
更名,只能获取到file
数据自己的数据信息,而没法获取ctx
的上下文信息(如,这里准备根据请求参数建立目录存储文件)。npm
// 更新文件:server/control/assets.js const fs = require('fs'); const path = require('path'); const { checkDirExist } = require('../utils/dir'); async function upload (ctx, next) { const file = Object.values(ctx.request.files)[0]; const { category, id } = ctx.params; const filePath = file.path; // 最终要保存到的文件夹路径 const dir = path.join(__dirname,`../public/${category}/${id}/`); try { // 检查文件夹是否存在——>若是不存在,则新建文件夹 checkDirExist(dir); const reader = fs.createReadStream(filePath); const writer = fs.createWriteStream(path.resolve(dir, file.name)); reader.pipe(writer); // 删除缓存文件 fs.unlinkSync(filePath) } catch (err) { } } module.exports = { upload }
avatar
目录下。Postman
测试结果:c#
这时候访问文件:
因此,须要将public
目录添加到身份认证的unless
白名单中:缓存
// 更新文件:server/app.js ... custom: function(ctx) { const { method, path, query } = ctx; if(path === '/'){ return true; } if(/^\\/public/.test(path)) { // public目录 return true; } if(path === '/users' && query.action) { return true; } return false; } ...
继续访问:app
这是由于默认会请求动态资源,而图片数据静态资源less
// 更新文件: ... const koaStatic = require('koa-static'); ... // 中间件:指定静态资源路径 vs. 使其与动态资源分离 app.use(koaStatic(path.join(__dirname, 'public/')))
继续访问,报错如上:koa
这是由于访问路径错了:访问路径不用带/public
若怀疑,不加koa-static
,仅修改路径便可访问的能够自行试试。
上述内容中,修改了upload
的业务逻辑,仅涉及到将文件保存到指定路径下,却没有去更新用户头像信息,而且,服务端没有返回数据。
// 更新文件:server/control/assets.js const fs = require('fs'); const path = require('path'); const userModel = require('../model/user'); const { checkDirExist } = require('../utils/dir'); async function upload (ctx, next) { const file = Object.values(ctx.request.files)[0]; const { category, id } = ctx.params; // 用户头像远程地址 const remotePath = `${ctx.origin}/${category}/${id}/${file.name}`; const filePath = file.path; const dir = path.join(__dirname,`../public/${category}/${id}/`); try { checkDirExist(dir); const reader = fs.createReadStream(filePath); const writer = fs.createWriteStream(path.resolve(dir, file.name)); reader.pipe(writer); try { // 更新用户头像信息 await userModel.updateOne( { _id: id }, { avatar: remotePath } ).exec(); } catch (err) { ctx.body = { code: '404', data: null, msg: '上传失败' }; return; } fs.unlinkSync(filePath) ctx.body = { code: '200', data: { filePath: remotePath }, msg: '上传成功' } } catch (err) { ctx.body = { code: '404', data: null, msg: '上传失败' } } } module.exports = { upload }
${ctx.origin}/${category}/${id}/${file.name}
,并将该地址更新到用户信息中。这里将/server/public
目录删除,从新测试:
//更新文件: ... methods: { ... handleAvatarSuccess (res, file) { this.imageUrl = URL.createObjectURL(file.raw) }, beforeAvatarUpload (file) { const isJPG = /^image\//.test(file.type) const isLt2M = file.size / 1024 / 1024 / 10 < 2 if (!isJPG) { this.$message.error('上传头像图片只能是 JPG 格式!') } if (!isLt2M) { this.$message.error('上传头像图片大小不能超过 20MB!') } return isJPG && isLt2M }, ... data () { return { uploadForm: { action: `//localhost:3000/assets/avatars/${this.$store.state.loginer.id}`, headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, multiple: false, 'show-file-list': false, 'on-success': this.handleAvatarSuccess, 'before-upload': this.beforeAvatarUpload }, ... async created () { const res = await http.get(`/users/${this.userId}`) if (res.code === '200') { this.loginer = res.data this.dialogForm.form = {...res.data} this.imageUrl = res.data.avatar //初始化时,赋值 } else { this.$message({ type: 'error', message: '获取用户信息失败' }) } }
uploadForm
设置上传配置on-success
上传成功时,将图片地址覆盖原有值;before-upload
上传以前,校验文件类型和大小;效果展现: