滬江CCtalk視訊地址:www.cctalk.com/v/151149238…
規範與部署
懶人推動社會進步。
本篇中,我們會講述三個知識點
- 定製書寫規範
- 開發環境執行
- 如何部署執行
定製書寫規範
文中所說的書寫規範,僅供參考,非專案必需。
隨著 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,呼叫中介軟體,啟動路由等
複製程式碼
當架構師準備好專案結構後,開發人員只需要修改業務層面的程式碼即可,比如當我們增加一個業務場景時候,我們大概需要修改三個地方:
service/
目錄下新建檔案,處理邏輯層的業務程式碼,並返回給controller
層controller/
目錄下新建檔案,簡單處理下請求資料後,傳遞給service
- 修改路由檔案
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
複製程式碼
更多細節用法,請查閱官方文件
推薦: 翻譯專案Master的自述:
1. 乾貨|人人都是翻譯專案的Master
2. iKcamp出品微信小程式教學共5章16小節彙總(含視訊)
2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!