iKcamp|基于Koa2搭建Node.js實戰(含視頻)? 規范與部署

滬江CCtalk視頻地址:https://www.cctalk.com/v/15114923889450

5.png

規范與部署

懶人推動社會進步。

本篇中,我們會講述三個知識點

  • 定制書寫規范
  • 開發環境運行
  • 如何部署運行

定制書寫規范

文中所說的書寫規范,僅供參考,非項目必需。

隨著 Node 流行,JavaScript 編碼規范已經相當成熟,社區也產生了各種各樣的編碼規范。但是在這里,我們要做的不是『限制空格的數量』,也不是『要不要加分號』。我們想要說的規范,是項目結構的規范。

目前我們的項目結構如下:

├─ controller/          // 用于解析用戶的輸入,處理后返回相應的結果
├─ service/             // 用于編寫業務邏輯層,比如連接數據庫,調用第三方接口等
├─ errorPage/           // http 請求錯誤時候,對應的錯誤響應頁面
├─ logs/                // 項目運用中產生的日志數據
├─ middleware/          // 中間件集中地,用于編寫中間件,并集中調用
│  ├─ mi-http-error/
│  ├─ mi-log/
│  ├─ mi-send/
│  └── index.js
├─ public/              // 用于放置靜態資源
├─ views/               // 用于放置模板文件,返回客戶端的視圖層
├─ router.js            // 配置 URL 路由規則
└─ app.js               // 用于自定義啟動時的初始化工作,比如啟動 https,調用中間件,啟動路由等

當架構師準備好項目結構后,開發人員只需要修改業務層面的代碼即可,比如當我們增加一個業務場景時候,我們大概需要修改三個地方:

  1. service/ 目錄下新建文件,處理邏輯層的業務代碼,并返回給 controller
  2. controller/ 目錄下新建文件,簡單處理下請求數據后,傳遞給 service
  3. 修改路由文件 router.js,增加路由對應的處理器

隨著業務量的增大,我們就會發現有一個重復性的操作——『不斷的 require 文件,不斷的解析文件中的函數』。當業務量達到一定程度時候,可能一個文件里面要額外引入十幾個外部文件:

const controller1 = require('...')
const controller2 = require('...')
const controller3 = require('...')
const controller4 = require('...')
...
app.get('/fn1', controller1.fn1() )
app.get('/fn2', controller2.fn2() )
app.get('/fn3', controller3.fn3() )
app.get('/fn4', controller4.fn4() )

單是起名字就已經夠頭疼的!

所以,我們要做的事情就是,約定代碼結構規范,省去這些頭疼的事情,比如 router.js

// const router = require('koa-router')()
// const HomeController = require('./controller/home')
// module.exports = (app) => {
//   router.get( '/', HomeController.index )
//   router.get('/home', HomeController.home)
//   router.get('/home/:id/:name', HomeController.homeParams)
//   router.get('/user', HomeController.login)
//   router.post('/user/register', HomeController.register)
//   app.use(router.routes())
//      .use(router.allowedMethods())
// }
const router = require('koa-router')()
module.exports = (app) => {
  router.get( '/', app.controller.home.index )
  router.get('/home', app.controller.home.home)
  router.get('/home/:id/:name', app.controller.home.homeParams)
  router.get('/user', app.controller.home.login)
  router.post('/user/register', app.controller.home.register)
  app.use(router.routes())
     .use(router.allowedMethods())
}

聰明的同學可能已經發現了,app.controller.home.index 其實就是 cotroller/home.js 中的 index 函數。

設計思路

實現思路很簡單,當應用程序啟動時候,讀取指定目錄下的 js 文件,以文件名作為屬性名,掛載在實例 app 上,然后把文件中的接口函數,擴展到文件對象上。

一般有兩種方式入手,一種是程序啟動時候去執行,另外一種是請求過來時候再去讀取。

而在傳統書寫方式中,項目啟動時候會根據 require 加載指定目錄文件,然后緩存起來,其思路與第一種方式一致。如果以中間件的方式,在請求過來時候再去讀取,則第一次讀取肯定會相對慢一起。綜合考慮,我們采用了第一種方式:程序啟動時候讀取。

代碼實現

新建目錄文件 middleware/mi-rule/index.js, 實現代碼如下:

const Path = require("path");
const fs = require('fs');
module.exports = function (opts) {
  let { app, rules = []} = opts

  // 如果參數缺少實例 app,則拋出錯誤
  if (!app) {
    throw new Error("the app params is necessary!")
  }
  // 提取出 app 實例對象中的屬性名
  const appKeys = Object.keys(app)
  rules.forEach((item) => {
    let { path, name} = item
    // 如果 app 實例中已經存在了傳入過來的屬性名,則拋出錯誤
    if (appKeys.includes(name)) {
      throw new Error(`the name of ${name} already exists!`)
    }
    let content = {};
    //讀取指定文件夾下(dir)的所有文件并遍歷
    fs.readdirSync(path).forEach(filename => {
      //取出文件的后綴
      let extname = Path.extname(filename);
      //只處理js文件
      if (extname === '.js') {
        //將文件名中去掉后綴
        let name = Path.basename(filename, extname);
        //讀取文件中的內容并賦值綁定
        content[name] = require(Path.join(path, filename));
      }
    });
    app[name] = content
  })
}

opts 是參數對象,里面包含了實例 app,用來掛載指定的目錄文件。rules 是我們指定的目錄規則。

用法如下,修改 middleware/index.js

// 引入規則中件間
const miRule = require('./mi-rule')

module.exports = (app) => {
  /**
   * 在接口的開頭調用
   * 指定 controller 文件夾下的 js 文件,掛載在 app.controller 屬性
   * 指定 service 文件夾下的 js 文件,掛載在 app.service 屬性
   */ 
  miRule({
    app,
    rules: [
      {
        path: path.join(__dirname, '../controller'),
        name: 'controller'
      },
      {
        path: path.join(__dirname, '../service'),
        name: 'service'
      }
    ]
  })

  // 以下代碼省略
}

業務代碼應用

1. 修改 router.js

const router = require('koa-router')()
module.exports = (app) => {
  router.get( '/', app.controller.home.index )
  router.get('/home', app.controller.home.home)
  router.get('/home/:id/:name', app.controller.home.homeParams)
  router.get('/user', app.controller.home.login)
  router.post('/user/register', app.controller.home.register)
  app.use(router.routes()).use(router.allowedMethods())
}

2. 修改 controller/home.js

module.exports = {
  index: async(ctx, next) => {
    await ctx.render("home/index", {title: "iKcamp歡迎您"})
  },
  home: async(ctx, next) => {
    ctx.response.body = '<h1>HOME page</h1>'
  },
  homeParams: async(ctx, next) => {
    ctx.response.body = '<h1>HOME page /:id/:name</h1>'
  },
  login: async(ctx, next) => {
    await ctx.render('home/login', {
      btnName: 'GoGoGo'
    })
  },
  register: async(ctx, next) => {
    // 解構出 app 實例對象
    const { app } = ctx

    let params = ctx.request.body
    let name = params.name
    let password = params.password

    // 留意 service 層的調用方式
    let res = await app.service.home.register(name,password)
    if(res.status == "-1"){
      await ctx.render("home/login", res.data)
    }else{
      ctx.state.title = "個人中心"
      await ctx.render("home/success", res.data)
    }
  }
}

項目中引入這個結構規范,并不是必須的,畢竟大家的想法不一樣。iKcamp 團隊在提出此想法時候,也是有不少分歧。提出這樣一個思路,僅供大家參考。

開發環境運行

作為后端代碼語言,開發環境中每次修改文件,都需要手動的重啟應用,不能像前端瀏覽器那樣清爽。為了減輕手工重啟的成本,我們建議采用 nodemon 來代替 node 以啟動應用。當代碼發生變化時候,nodemon 會幫我們自動重啟。

全局安裝 nodemon

npm i nodemon -g

本地項目中也需要安裝:

npm i nodemon -S

更多細節用法,請查閱官方文檔

部署運行

線上部署運行的話,方法也有很多,我們推薦使用 pm2

pm2 是一個帶有負載均衡功能的Node應用的進程管理器。

安裝方法與 nodemon 相似,需要全局安裝:

npm i pm2 -g

運行方法:

pm2 start app.js

更多細節用法,請查閱官方文檔

移動Web前端高效開發實戰.png

推薦: 翻譯項目Master的自述:

1. 干貨|人人都是翻譯項目的Master

2. iKcamp出品微信小程序教學共5章16小節匯總(含視頻)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,150評論 25 708
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,760評論 18 399
  • 一,tuple,可以裝任何對象,沒有順序,取值用[]; 二,RACSequenceb遍歷數組和字典 1,遍歷數組 ...
    張不二01閱讀 618評論 0 0
  • 001進度:今天沒看書,煩其他事(借口) 002還是那句,能用錢解決的問題不是我能解決的問題,果然經濟是決定一切的...
    K少628閱讀 150評論 0 0
  • 在你努力奮斗,百折不撓的時候,記得照看好你心中的那個小女人。
    ShayleeLi閱讀 239評論 2 8