koa2 從入坑到放棄


koa2 從入坑到放棄

為啥入坑,Express 原班人馬打造 更小、更健壯、更富有表現(xiàn)力

一直很想研究下koa2,最近得空,加上自己擠出來的時間,終于入坑了koa2。由于之前有過一些express經(jīng)驗,開發(fā)過一些后端的東西。所以以為koa還是很好上手的,但是用起來發(fā)現(xiàn)懵逼了,雖然大致結(jié)構(gòu)上差不多,但是一些方法的細節(jié)還是有些差別的。重大的差別就是response, 另外采用了es6語法,在寫法上更加的飄逸。為了避免剛?cè)肟拥男』锇榕啦怀鰜恚虼苏泶宋摹?/p>


項目構(gòu)建

先介紹下目錄結(jié)構(gòu),如下

├── README.md 項目描述
├── app  業(yè)務側(cè)代碼
│   ├── controller 與路由關(guān)聯(lián)的api方法
│   └── modal 數(shù)據(jù)模型
├── app.js 入口文件
├── bin nodemon
│   ├── run  nodemon 的入口文件
│   └── www
├── config 配置文件
│   ├── dbConfig.js 數(shù)據(jù)庫配置
│   ├── logConfig.js 日志配置 
│   └── serverConfig.js 服務配置
├── logs  日志目錄
│   ├── error 錯誤日志
│   └── response 普通響應日志 (還可以繼續(xù)拆分,系統(tǒng)日志,業(yè)務日志)
├── middleware  中間件
│   └── loggers.js  日志中間件
├── public
│   └── stylesheets 公用文件
├── routes  路由
│   ├── allRoute.js 總路由配置
│   ├── files.js 各個模塊路由配置
│   ├── index.js
│   └── users.js
├── uploads 上傳文件夾
│   └── 2017-8-29
├── utils 公用方法
│   ├── logUtil.js 
│   └── mkdir.js
├── views 頁面層
│   ├── error.jade
│   ├── index.jade
│   └── layout.jade
└── package.json


tree 目錄生成命令

tree -L 3 -I "node_modules"

brew install tree  ||  apt-get install tree
  • tree -d 只顯示文件夾;
  • tree -L n 顯示項目的層級。n表示層級數(shù)。比如想要顯示項目三層結(jié)構(gòu),可以用tree -l 3;
  • tree -I pattern 用于過濾不想要顯示的文件或者文件夾。比如你想要過濾項目中的
  • node_modules文件夾,可以使用tree -I “node_modules”;
  • tree > tree.md 將項目結(jié)構(gòu)輸出到tree.md這個文件。

首先是寫法

之前用express的時候,用的是es5的語法規(guī)范
koa2用采用了es6,7的新特性,盡情的使用let吧
nodemon babelrc的福音,自動轉(zhuǎn)碼,不用配置.babelrc, 也不需要再裝一些列bable轉(zhuǎn)碼了。

寫異步
以前是.then方法里的各種callback

exports.getUserList = function() { 
    user.find({
     _id: id,
    }, arr, function(e, numberAffected, raw) {
      if(e){
          respondata={
            "code":"9900",
            "message":"error"
          };
      }else{
          respondata={
            "code":"0000",
            "message":"success"
          };
      }
    });
}

現(xiàn)在可以用 async await

exports.getUserList = async (ctx, next) => {
    try {
        let list = await user.find();
        let respon = {
            code: '0000',
            message: 'success',
            data: list
        }
        return respon;
    } catch (err) {
        let respon = {
            code: '9999',
            message: 'error',
            data: err
        }
        return respon;
    }
}

因為后端的很多操作方法,比如文件,數(shù)據(jù)庫,都是異步的,所以這種將異步寫法變?yōu)橥綄懛?,是代碼的可讀性大大提高。


Route 路由

koa-route采用的是restful設(shè)計模式,可以參考阮一峰老師的《RESTful API 設(shè)計指南》 http://www.ruanyifeng.com/blog/2014/05/restful_api.html
路由的模塊化 路由規(guī)則是域名+模塊+方法
例如:localhost:8080/users/getUser

<allroute.js>

const router = require('koa-router')();
const index = require('./index');
const users = require('./users');
const files = require('./files');

router.use('/', index.routes(), index.allowedMethods());
router.use('/users', users.routes(), users.allowedMethods());
router.use('/files', files.routes(), files.allowedMethods());

module.exports = router;


<users.js>
const router = require('koa-router')();
import {getUserList, register, removeUser} from '../app/controller/user'

router.get('/', function (ctx, next) {
  ctx.body = 'this a users response!';
});
router.get('/getUser', async (ctx, next) => {
  ctx.body = await getUserList(ctx, next);
});
router.post('/register', async (ctx, next) => {
  console.log(ctx.request.body);
  let reqBody = ctx.request.body;
  ctx.body = await register(reqBody);
});
router.del('/removeUser', async (ctx, next) => {
  console.log(ctx.request.body);
  let reqBody = ctx.request.body;
  ctx.body = await removeUser(reqBody);
});
module.exports = router;

reseful的路由,如果你的請求方式不是get | post | del,或者與其不匹配,統(tǒng)一返回404 not found


Middleware 中間件

中間件就是類似于一個過濾器的東西,在客戶端和應用程序之間的一個處理請求和響應的的方法。

.middleware1 {
  // (1) do some stuff
  .middleware2 {
    // (2) do some other stuff
    .middleware3 {
      // (3) NO next yield !
      // this.body = 'hello world'
    }
    // (4) do some other stuff later
  }
  // (5) do some stuff lastest and return
}

中間件的執(zhí)行很像一個洋蔥,但并不是一層一層的執(zhí)行,而是以next為分界,先執(zhí)行本層中next以前的部分,當下一層中間件執(zhí)行完后,再執(zhí)行本層next以后的部分。


let koa = require('koa');
let app = new koa();

app.use((ctx, next) => {
  console.log(1)
  next(); // next不寫會報錯
  console.log(5)
});

app.use((ctx, next) => {
  console.log(2)
  next();
  console.log(4)
});

app.use((ctx, next) => {
  console.log(3)
  ctx.body = 'Hello World';
});

app.listen(3000);
// 打印出1、2、3、4、5

上述簡單的應用打印出1、2、3、4、5,這個其實就是koa中間件控制的核心,一個洋蔥結(jié)構(gòu),從上往下一層一層進來,再從下往上一層一層回去,乍一看很復雜,為什么不直接一層一層下來就結(jié)束呢,就像express/connect一樣,我們就只要next就去下一個中間件,干嘛還要回來?

其實這就是為了解決復雜應用中頻繁的回調(diào)而設(shè)計的級聯(lián)代碼,并不直接把控制權(quán)完全交給下一個中間件,而是碰到next去下一個中間件,等下面都執(zhí)行完了,還會執(zhí)行next以下的內(nèi)容

解決頻繁的回調(diào),這又有什么依據(jù)呢?舉個簡單的例子,假如我們需要知道穿過中間件的時間,我們使用koa可以輕松地寫出來,但是使用express呢,可以去看下express reponse-time的源碼,它就只能通過監(jiān)聽header被write out的時候然后觸發(fā)回調(diào)函數(shù)計算時間,但是koa完全不用寫callback,我們只需要在next后面加幾行代碼就解決了(直接使用.then()都可以)


Logs 日志

log4js接入及使用方法

let log4js = require('log4js');

let logConfig = require('../config/logConfig');

//加載配置文件
log4js.configure(logConfig);

let logUtil = {};

let errorLogger = log4js.getLogger('error'); //categories的元素
let resLogger = log4js.getLogger('response');

//封裝錯誤日志
logUtil.logError = function (ctx, error, resTime) {
    if (ctx && error) {
        errorLogger.error(formatError(ctx, error, resTime));
    }
};

//封裝響應日志
logUtil.logResponse = function (ctx, resTime) {
    if (ctx) {
        resLogger.info(formatRes(ctx, resTime));
    }
};

config : {
    "appenders":{
        error: {
            "category":"errorLogger",             //logger名稱
            "type": "dateFile",                   //日志類型
            "filename": errorLogPath,             //日志輸出位置
            "alwaysIncludePattern":true,          //是否總是有后綴名
            "pattern": "-yyyy-MM-dd-hh.log",      //后綴,每小時創(chuàng)建一個新的日志文件
            "path": errorPath  
        },
        response: {
            "category":"resLogger",
            "type": "dateFile",
            "filename": responseLogPath,
            "alwaysIncludePattern":true,
            "pattern": "-yyyy-MM-dd-hh.log",
            "path": responsePath,
        }
    },
    "categories" : { 
        error: { appenders: ['error'], level: 'error' },
        response: { appenders: ['response'], level: 'info' },
        default: { appenders: ['response'], level: 'info' },
    }
}

File 文件系統(tǒng)

nodejs 文件 I/O 是對標準 POSIX 函數(shù)的簡單封裝。 通過 require(‘fs’) 使用該模塊。 所有的方法都有異步和同步的形式。

異步方法的最后一個參數(shù)都是一個回調(diào)函數(shù)。 傳給回調(diào)函數(shù)的參數(shù)取決于具體方法,但回調(diào)函數(shù)的第一個參數(shù)都會保留給異常。 如果操作成功完成,則第一個參數(shù)會是 null 或 undefined。

當使用同步方法時,任何異常都會被立即拋出。 可以使用 try/catch 來處理異常,或讓異常向上冒泡。

比如要做一個圖片上傳和圖片展示的功能,需要用到以下幾個方法

existsSync 檢測文件是否存在(同步方法)
mkdirsSync 創(chuàng)建目錄(同步方法)
readFileSync 讀取文件
createWriteStream 創(chuàng)建一個寫入流
createReadStream 創(chuàng)建一個讀取流
unlinkSync 文件刪除(同步方法)

文件上傳步驟

1.拿到上傳的file對象
2.規(guī)定好文件存放的路徑
3.創(chuàng)建目標路徑的寫入流和file.path(緩存路徑)的讀入流
4.以讀入流為基礎(chǔ)放入寫入流中
5.刪除緩存路徑的文件
6.數(shù)據(jù)庫記錄

file = ctx.request.body.files 
targetInfo = getFileInfo(type);

tmpPath = file.path;
type = file.type;
    
targetInfo = getFileInfo(type);

// targetInfo 包含 {targetName 文件名稱,targetPaths 全路徑目標目錄, resultPath 加上文件名的目標目錄, relativePath 相對路徑目標目錄}

mkdirs.mkdirsSync(targetInfo.targetPaths); // 目錄
stream = fs.createWriteStream(targetInfo.resultPath);//創(chuàng)建一個可寫流  
fs.createReadStream(tmpPath).pipe(stream);
unlinkStatus = fs.unlinkSync(tmpPath);

獲取文件
通過readFileSync 拿到Buffer形式的文件

獲取文件的路徑
filepath = files.find({_id: id}); //通過查詢數(shù)據(jù)庫拿到
ctx.body = fs.readFileSync(filepath);
ctx.res.writeHead(200, {'Content-Type': 'image/png'});

mongodb crud 數(shù)據(jù)庫

connect 數(shù)據(jù)庫連接

let dbName = "nodeapi";
let dbHost = "mongodb://localhost/";
let mongoose = require("mongoose");
exports.connect = function(request, response) {
    mongoose.connect(dbHost + dbName, { useMongoClient: true }); // useMongoClient防止報錯
    let db = mongoose.connection;
    db.on('error', console.error.bind(console, 'connection error:'));
    db.once('open', function (callback) {
      console.log('connet success!');
    });
}

mongoose.Schema 字段對象模式

增刪改查 modal

let mongoose = require("mongoose");
let Schema = mongoose.Schema;
let FilesSchema = new Schema({
    fileName: String,
    filePath: String,
    content: String,
    createTime: {
        type: Date,
        dafault: Date.now()
    },
    updateTime: {
        type: Date,
        dafault: Date.now()
    },
})

FilesSchema.pre('save', function(next) {
    if (this.isNew) {
      this.createTime = this.updateTime = Date.now()
    }
    else {
      this.updateTime = Date.now()
    }
    next()
})
class Files{
    constructor() {
          this.files = mongoose.model("files", FilesSchema);
    }
    find(dataArr={}) {
        const self = this;
        return new Promise(function (resolve, reject){
            self.files.find(dataArr, function(e, docs) {
                if(e){
                    console.log('e:',e);
                    reject(e);
                }else{
                    resolve(docs);
                }
            })
        })
    }
    create(dataArr) {
        const self = this;
        return new Promise(function (resolve, reject){
            let user = new self.files({
                fileName: dataArr.fileName,
                filePath: dataArr.filePath,
                content: dataArr.content,
            });
            user.save(function(e, data, numberAffected) {
                // if (e) response.send(e.message);
                if(e){
                    reject(e);
                }else{
                    resolve(data);
                }
            });
        })
    }
    delete(dataArr) {
        const self = this;
        return new Promise(function (resolve, reject){
            self.files.remove({
                _id: dataArr.id
            }, function(e, data) {
                if(e){
                    reject(e);
                }else{
                    resolve(data);
                }
            });
        })
    }
}

let files = new Files()
export {files}

以模塊的形式進行封裝,可以更方便外層調(diào)用

async 異步寫操作數(shù)據(jù)庫

import {files} from '../modal/files'
readFile =  async (id) => {
    try {
        let list = await files.find({_id: id});
        console.log(list)
        if(list && list.length > 0) {
            return fs.readFileSync(list[0].content);   
        } else {
            return errdata(null,'9999', 'can not find file')
        }
    } catch (err) {
        return errdata(err);
    }
}

寫在最后

此項目僅供大家的學習與參考,歡迎多多交流~微信 <img style=“width:30%” src=“http://47.88.2.72:2016/getphotoPal/2017-9-5/15046213222746.png”/>
原文地址 https://github.com/liuyahuiZ/server-koa
koa2學習地址參考
https://github.com/guo-yu/koa-guide

本文搬運自https://cnodejs.org/topic/59af58f09cd61679045b9551。一方面為了分享好文,另一方面也是為了自己學習的時候查閱起來方便。侵刪致歉!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容