Koa & Mongoose & Vue實現先後端分離--11更新用戶頭像

上節回顧

  • 更新用戶文本數據

工做內容

  • 更新用戶數據
  • 圖片上傳 & 存儲 & 靜態化訪問

準備工做

  • 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

upload.gif

請求結果:沒有任何返回。
vs code的調試控制檯反饋:
error.png
設置存儲文件的路徑不存在。前端

保存文件

存儲路徑不存在,須要檢測文件目錄是否存在,若不存在,則新建。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,
}

方式1、經過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,能夠在處理文件以前,進行一些操做,如:測試目錄是否存在、更名、改存儲路徑等。

測試結果:
filenamegithub

方式2、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
}
  • 這裏,根據上傳路由的參數,將文件存到用戶ID建立的avatar目錄下。

Postman測試結果:
avatarc#

訪問文件

這時候訪問文件:
error
因此,須要將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;
}
...

繼續訪問:
image.pngapp

這是由於默認會請求動態資源,而圖片數據靜態資源less

// 更新文件:
...
const koaStatic =  require('koa-static');
...
// 中間件:指定靜態資源路徑 vs. 使其與動態資源分離
app.use(koaStatic(path.join(__dirname, 'public/')))

繼續訪問,報錯如上:
image.pngkoa

這是由於訪問路徑錯了:訪問路徑不用帶/public
dir.gif

若懷疑,不加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目錄刪除,從新測試:
sucess.gif

前端頁面頭像邏輯

//更新文件:
...
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上傳以前,校驗文件類型和大小;

效果展現:
fronEnd.gif

參考文檔

koa-static
koa-body上傳文件相關配置

相關文章
相關標籤/搜索