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
上傳以前,校驗文件類型和大小;效果展現: