滬江CCtalk視頻地址:https://www.cctalk.com/v/15114923889450前端
懶人推進社會進步。node
本篇中,咱們會講述三個知識點git
文中所說的書寫規範,僅供參考,非項目必需。github
隨着 Node
流行,JavaScript
編碼規範已經至關成熟,社區也產生了各類各樣的編碼規範。可是在這裏,咱們要作的不是『限制空格的數量』,也不是『要不要加分號』。咱們想要說的規範,是項目結構的規範。數據庫
目前咱們的項目結構以下:npm
├─ 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' } ] }) // 如下代碼省略 }
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()) }
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
更多細節用法,請查閱官方文檔