iKcamp|基于Koa2搭建Node.js实战(含视频)☞ 规范与部署
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
滬江CCtalk視頻地址:https://www.cctalk.com/v/15114923889450
規(guī)范與部署
懶人推動社會進(jìn)步。
本篇中,我們會講述三個知識點(diǎn)
- 定制書寫規(guī)范
- 開發(fā)環(huán)境運(yùn)行
- 如何部署運(yùn)行
定制書寫規(guī)范
文中所說的書寫規(guī)范,僅供參考,非項(xiàng)目必需。
隨著 Node 流行,JavaScript 編碼規(guī)范已經(jīng)相當(dāng)成熟,社區(qū)也產(chǎn)生了各種各樣的編碼規(guī)范。但是在這里,我們要做的不是『限制空格的數(shù)量』,也不是『要不要加分號』。我們想要說的規(guī)范,是項(xiàng)目結(jié)構(gòu)的規(guī)范。
目前我們的項(xiàng)目結(jié)構(gòu)如下:
├─ controller/ // 用于解析用戶的輸入,處理后返回相應(yīng)的結(jié)果 ├─ service/ // 用于編寫業(yè)務(wù)邏輯層,比如連接數(shù)據(jù)庫,調(diào)用第三方接口等 ├─ errorPage/ // http 請求錯誤時候,對應(yīng)的錯誤響應(yīng)頁面 ├─ logs/ // 項(xiàng)目運(yùn)用中產(chǎn)生的日志數(shù)據(jù) ├─ middleware/ // 中間件集中地,用于編寫中間件,并集中調(diào)用 │ ├─ mi-http-error/ │ ├─ mi-log/ │ ├─ mi-send/ │ └── index.js ├─ public/ // 用于放置靜態(tài)資源 ├─ views/ // 用于放置模板文件,返回客戶端的視圖層 ├─ router.js // 配置 URL 路由規(guī)則 └─ app.js // 用于自定義啟動時的初始化工作,比如啟動 https,調(diào)用中間件,啟動路由等當(dāng)架構(gòu)師準(zhǔn)備好項(xiàng)目結(jié)構(gòu)后,開發(fā)人員只需要修改業(yè)務(wù)層面的代碼即可,比如當(dāng)我們增加一個業(yè)務(wù)場景時候,我們大概需要修改三個地方:
隨著業(yè)務(wù)量的增大,我們就會發(fā)現(xiàn)有一個重復(fù)性的操作——『不斷的 require 文件,不斷的解析文件中的函數(shù)』。當(dāng)業(yè)務(wù)量達(dá)到一定程度時候,可能一個文件里面要額外引入十幾個外部文件:
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() )單是起名字就已經(jīng)夠頭疼的!
所以,我們要做的事情就是,約定代碼結(jié)構(gòu)規(guī)范,省去這些頭疼的事情,比如 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()) }聰明的同學(xué)可能已經(jīng)發(fā)現(xiàn)了,app.controller.home.index 其實(shí)就是 cotroller/home.js 中的 index 函數(shù)。
設(shè)計思路
實(shí)現(xiàn)思路很簡單,當(dāng)應(yīng)用程序啟動時候,讀取指定目錄下的 js 文件,以文件名作為屬性名,掛載在實(shí)例 app 上,然后把文件中的接口函數(shù),擴(kuò)展到文件對象上。
一般有兩種方式入手,一種是程序啟動時候去執(zhí)行,另外一種是請求過來時候再去讀取。
而在傳統(tǒng)書寫方式中,項(xiàng)目啟動時候會根據(jù) require 加載指定目錄文件,然后緩存起來,其思路與第一種方式一致。如果以中間件的方式,在請求過來時候再去讀取,則第一次讀取肯定會相對慢一起。綜合考慮,我們采用了第一種方式:程序啟動時候讀取。
代碼實(shí)現(xiàn)
新建目錄文件 middleware/mi-rule/index.js, 實(shí)現(xiàn)代碼如下:
const Path = require("path"); const fs = require('fs'); module.exports = function (opts) {let { app, rules = []} = opts// 如果參數(shù)缺少實(shí)例 app,則拋出錯誤if (!app) {throw new Error("the app params is necessary!")}// 提取出 app 實(shí)例對象中的屬性名const appKeys = Object.keys(app)rules.forEach((item) => {let { path, name} = item// 如果 app 實(shí)例中已經(jīng)存在了傳入過來的屬性名,則拋出錯誤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);//讀取文件中的內(nèi)容并賦值綁定content[name] = require(Path.join(path, filename));}});app[name] = content}) }opts 是參數(shù)對象,里面包含了實(shí)例 app,用來掛載指定的目錄文件。rules 是我們指定的目錄規(guī)則。
用法如下,修改 middleware/index.js:
// 引入規(guī)則中件間 const miRule = require('./mi-rule')module.exports = (app) => {/*** 在接口的開頭調(diào)用* 指定 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'}]})// 以下代碼省略 }業(yè)務(wù)代碼應(yīng)用
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) => {// 解構(gòu)出 app 實(shí)例對象const { app } = ctxlet params = ctx.request.bodylet name = params.namelet password = params.password// 留意 service 層的調(diào)用方式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)}} }項(xiàng)目中引入這個結(jié)構(gòu)規(guī)范,并不是必須的,畢竟大家的想法不一樣。iKcamp 團(tuán)隊(duì)在提出此想法時候,也是有不少分歧。提出這樣一個思路,僅供大家參考。
開發(fā)環(huán)境運(yùn)行
作為后端代碼語言,開發(fā)環(huán)境中每次修改文件,都需要手動的重啟應(yīng)用,不能像前端瀏覽器那樣清爽。為了減輕手工重啟的成本,我們建議采用 nodemon 來代替 node 以啟動應(yīng)用。當(dāng)代碼發(fā)生變化時候,nodemon 會幫我們自動重啟。
全局安裝 nodemon:
npm i nodemon -g本地項(xiàng)目中也需要安裝:
npm i nodemon -S更多細(xì)節(jié)用法,請查閱官方文檔
部署運(yùn)行
線上部署運(yùn)行的話,方法也有很多,我們推薦使用 pm2。
pm2 是一個帶有負(fù)載均衡功能的Node應(yīng)用的進(jìn)程管理器。
安裝方法與 nodemon 相似,需要全局安裝:
npm i pm2 -g運(yùn)行方法:
pm2 start app.js更多細(xì)節(jié)用法,請查閱官方文檔
推薦: 翻譯項(xiàng)目Master的自述:
1. 干貨|人人都是翻譯項(xiàng)目的Master
2. iKcamp出品微信小程序教學(xué)共5章16小節(jié)匯總(含視頻)
轉(zhuǎn)載于:https://my.oschina.net/ikcamp/blog/1619609
總結(jié)
以上是生活随笔為你收集整理的iKcamp|基于Koa2搭建Node.js实战(含视频)☞ 规范与部署的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手动建立Mysql表实体类技巧
- 下一篇: 10 过滤器和监听器