Doodoo.jsNode.js Web 快速開發(fā)框架
簡介
Doodoo.js -- 中文最佳實(shí)踐Node.js Web快速開發(fā)框架。支持Koa.js中間件,支持模塊化,插件,鉤子機(jī)制,可以直接在項(xiàng)目里使用 ES6/7(Generator Function, Class, Async & Await)等特性。同時(shí)吸收了thinkphp,laravel等國內(nèi)外眾多框架的設(shè)計(jì)理念和思想,讓開發(fā) Node.js 項(xiàng)目更加簡單、高效、靈活。
使用 ES6/7 特性來開發(fā)項(xiàng)目可以大大提高開發(fā)效率,是趨勢所在。并且新版的 Node.js 對 ES6 特性也有了較好的支持,即使有些特性還沒有支持,也可以借助 Babel 編譯來支持。
特性
支持koa全部中間件
支持使用 ES6+ 全部特性來開發(fā)項(xiàng)目
支持?jǐn)帱c(diǎn)調(diào)試 ES6+ 項(xiàng)目
支持多種項(xiàng)目結(jié)構(gòu)和多種項(xiàng)目環(huán)境
支持 Route, Controller 中使用Koa.js的所有API
支持多級 Controller
支持模塊化開發(fā)
支持鉤子機(jī)制
支持插件機(jī)制
支持錯(cuò)誤處理
支持全局doodoo變量
支持Bookshelf, knex鏈接數(shù)據(jù)庫
支持前置,后置操作
支持 Restful 設(shè)計(jì)
支持啟動自定義
支持環(huán)境加載配置
...
安裝
環(huán)境要求:node >= 7.6.0, git(doodoo.js的所有包都托管在github上)
//npm npm install doodooke/doodoo.js --save //yarn yarn add doodooke/doodoo.js
使用 ES6/7 特性來開發(fā)項(xiàng)目
//base controller, app/demo/controller/base.js
module.exports = class extends doodoo.Controller {
async _initialize() {
console.log('base _initialize');
}
async isLogin() {
console.log('base isLogin');
}
}
//index controller, app/demo/controller/index.js
const base = require('./base');
module.exports = class extends base {
async _initialize() {
await super._initialize();
}
async index() {
this.success("Hello Doodoo.js");
}
async index2() {
this.fail("Hello Doodoo.js");
}
}
項(xiàng)目中可以使用 ES6/7 里的特性,可以穩(wěn)定運(yùn)行在 >= 7.6.0 的 Node.js 環(huán)境中。
詳細(xì)的日志
服務(wù) 啟動日志
[doodoo] Version: 1.0.1 [doodoo] Website: 127.0.0.1 [doodoo] Nodejs Version: v10.5.0 [doodoo] Nodejs Platform: darwin x64 [doodoo] Server Enviroment: development [doodoo] Server Startup Time: 212ms [doodoo] Server Current Time: 2018-08-21 11:17:19 [doodoo] Server Running At: http://127.0.0.1:3000
HTTP 請求日志
<-- GET /home/index/index --> GET /home/index/index 200 4ms
與其他框架的對比
與 express/koa 對比
express/koa 是 2 個(gè)比較簡單的框架,框架本身提供的功能比較簡單,項(xiàng)目中需要借助大量的第三方插件才能完成項(xiàng)目的開發(fā),所以靈活度比較高。但使用很多第三方組件一方面提高了項(xiàng)目的復(fù)雜度。
koa 1.x 使用 ES6 里的 */yield 解決了異步回調(diào)的問題,但 */yield 只會是個(gè)過渡解決方案,會被 ES7 里的 async/await 所替代。
與 sails 對比
sails 也是一個(gè)提供整套解決方案的 Node.js 框架,對數(shù)據(jù)庫、REST API、安全方面也很多封裝,使用起來比較方便。
但 sails 對異步回調(diào)的問題還沒有優(yōu)化,還是使用 callback 的方式,給開發(fā)帶來很大的不便,導(dǎo)致項(xiàng)目中無法較好的使用 ES6/7 特性。
與 thinkjs 對比
thinkjs 是一個(gè)非常優(yōu)秀的框架,在開發(fā)效率和體驗(yàn)上占有絕對優(yōu)勢,但是中間件非常少,框架還比較新,缺少社區(qū)等方面的支持,還沒有經(jīng)過超大型項(xiàng)目的檢驗(yàn)。
創(chuàng)建項(xiàng)目
async/await
// 下載demo git clone https://github.com/doodooke/doodoo.js.git // 安裝依賴 yarn install // 進(jìn)入項(xiàng)目 cd doodoo.js/example // 啟動項(xiàng)目 node app.js
啟動項(xiàng)目
在項(xiàng)目目錄下執(zhí)行命令 node app.js,如果能看到類似下面的內(nèi)容,表示服務(wù)啟動成功。
[doodoo] Version: 1.0.1 [doodoo] Website: 127.0.0.1 [doodoo] Nodejs Version: v10.5.0 [doodoo] Nodejs Platform: darwin x64 [doodoo] Server Enviroment: development [doodoo] Server Startup Time: 212ms [doodoo] Server Current Time: 2018-08-21 11:17:19 [doodoo] Server Running At: http://127.0.0.1:3000
項(xiàng)目結(jié)構(gòu)
項(xiàng)目默認(rèn)使用的是mysql,redis數(shù)據(jù)庫。
|-- app | |-- demo | | |-- controller | | | |-- home | | | | |-- index.js | | | | `-- base.js | | | |-- admin | | |-- model | | |-- hook.js |-- logs |-- node_modules |-- www |-- app.js |-- package.json
app
源代碼目錄
app/demo/controller
模塊控制器目錄
app/demo/model
模塊模型目錄
app/demo/hook.js
模塊鉤子文件
www
靜態(tài)文件目錄,存放圖片,樣式等文件的目錄
app.js
項(xiàng)目啟動入口文件
代碼規(guī)范
大小寫規(guī)范
doodoo.js無倫是文件名還是控制器都默認(rèn)區(qū)分大小寫,很多在 Windows 下開發(fā)項(xiàng)目不區(qū)分大小寫,所以如果服務(wù)器環(huán)境是 Linux 要特別注意。
使用ES6語法
ES6 中有大量的語法糖可以簡化我們的代碼,讓代碼更加簡潔高效。 Node.js 最新版本已經(jīng)較好的支持了 ES6 的語法,即使有些語法不支持,也可以通過 Babel 編譯來支持。
constructor 方法
控制器 constructor 方法請盡量不要使用,推薦使用 _initialize ,如果需要使用,必須調(diào)用 super
module.exports = class doodoo.Controller {
constructor(ctx, next) {
super(ctx, next);
}
}
使用 Babel 編譯
雖然現(xiàn)在的 Node.js 版本已經(jīng)支持了很多 ES6 的特性,但這些特性現(xiàn)在還只是實(shí)現(xiàn)了,V8 里還沒有對這些特性進(jìn)行優(yōu)化。如:*/yield 等功能。
所以建議使用 Babel 來編譯,一方面可以使用 ES6 和 ES7 幾乎所有的特性,另一方面編譯后的性能也比默認(rèn)支持的要高。
使用 async/await
async/await 是nodejs異步最終解決方案
斷點(diǎn)調(diào)試
無論是在 VS Code(v1.7+) 下斷點(diǎn)調(diào)試,還是在WebStorm 下斷點(diǎn)調(diào)試,斷點(diǎn)一定要設(shè)置在 runtime 目錄下,不能設(shè)置在 app目錄下。
常見問題
為什么推薦 ES6/7 語法開發(fā)項(xiàng)目
ES6/7 里提供了大量的新特性,這些特性會帶來巨大的開發(fā)便利和效率上的提升。如:ES6 里的 */yield 和 ES7 里的 async/await 特性解決異步回調(diào)的問題;箭頭函數(shù)解決 this 作用域的問題;class 語法糖解決類繼承的問題。
雖然現(xiàn)在 Node.js 環(huán)境還沒有完全支持這些新的特性,但借助 Babel 編譯,可以穩(wěn)定運(yùn)行在現(xiàn)在的 Node.js 環(huán)境中。所以我們盡可以享受這些新特性帶來的便利。
如何修改服務(wù)監(jiān)聽的端口
默認(rèn)情況下,Node.js 服務(wù)監(jiān)聽的端口為 3000,如果需要修改的話,可以通過修改配置文件.env 來修改,如:
APP_PORT=3000
并行處理
使用 async/await 來處理異步時(shí),是串行執(zhí)行的。但很多場景下我們需要并行處理,這樣可以大大提高執(zhí)行效率,此時(shí)可以結(jié)合 Promise.all 來處理。
module.exports = class extends doodoo.Controller {
async index() {
let d1 = this.getData1();
let d2 = this.getData2();
let [d1Data, d2Data] = await Promise.all([d1, d2]);
}
}
如何輸出圖片
項(xiàng)目中有時(shí)候要輸出圖片等類型的數(shù)據(jù),可以通過下面的方式進(jìn)行:
module.exports = class extends doodoo.Controller {
async index() {
//圖片 buffer 數(shù)據(jù),讀取本地文件或者從遠(yuǎn)程獲取
let imageBuffer = new Buffer();
this.set('Content-Type', 'image/png');
this.view(imageBuffer);
}
}
用戶登錄后才能訪問
// app/demo/controller/home/base.js
module.exports = class extends doodoo.Controller {
async _initialize() {
await this.isLogin();
}
async isLogin() {
// 判斷登錄業(yè)務(wù)邏輯
}
}
// app/demo/controller/home/index.js
const base = require("./base");
module.exports = class extends base {
async _initialize() {
await super._initialize();
}
async index() {
this.success();
}
}
應(yīng)用
模塊
Doodoo.js 創(chuàng)建項(xiàng)目時(shí)支持多種項(xiàng)目模式,默認(rèn)創(chuàng)建的項(xiàng)目是按模塊來劃分的。使用模塊的方式劃分項(xiàng)目,可以讓項(xiàng)目結(jié)構(gòu)更加清晰。
模塊列表
app 下面的目錄就是模塊列表
控制器
控制器是一類操作的集合,用來響應(yīng)用戶同一類的請求。
定義控制器
創(chuàng)建文件 app/demo/controller/home/index.js,表示 demo 模塊下有名為 home/index 控制器,文件內(nèi)容類似如下:
module.exports = class extends doodoo.Controller {
async index (){
this.view('Hello World!');
}
}
常用控制器方法
ctx上的函數(shù)或參數(shù)將自動加載到Controller,例如支持 this.body = 'Hello World!', ctx中具體的API請參考Koa.js, Controller中的擴(kuò)展方法如下。
this.ctx; this.next; this.isGet(); this.isPost(); this.isAjax(); this.isPjax(); this.isMethod(method); this.hook.run(name, ...args); this.download(file); this.view(data); this.success(errmsg: "ok", errcode: 0, data: data); this.error(errmsg = "error", errcode = 1);
配置
默認(rèn)配置
# .env文件 # 應(yīng)用配置 APP_ROOT=app APP_PORT=3000 APP_HOST=127.0.0.1 # MYSQL數(shù)據(jù)庫鏈接 MYSQL=true MYSQL_HOST=127.0.0.1 MYSQL_USER=root MYSQL_PASSWORD=root MYSQL_DATABASE=doodoo MYSQL_PORT=3306 MYSQL_CHARSET=utf8mb4 # REDIS鏈接 REDIS=false REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PREFIX=doodoo: # 靜態(tài)資源服務(wù) STATIC_DIR=www STATIC_MAXAGE=30 * 24 * 60 * 60 STATIC_DYNAMIC=true
修改配置
創(chuàng)建 .env 配置文件,例如修改默認(rèn)啟動端口
APP_PORT=3000
路由
自動加載路由
Doodoo.js默認(rèn)使用了自動加載路由,類似于thinkphp的開發(fā)方式。例如訪問的路徑是 /demo/home/user/index,demo會解析成模塊,home/user會解析成控制器,index會解析成方法,此時(shí)會加載 app/demo/controller/home/user.js 下的index方法。
多級控制器
Doodoo.js支持多級控制器,例如訪問的路徑是 /demo/home/shop/product/index,demo會解析成模塊,home/shop/product會解析成控制器,index解析成方法,此時(shí)會加載 app/controller/home/shop/product.js 下的index方法。
鉤子
鉤子一般用于數(shù)據(jù)統(tǒng)計(jì),功能擴(kuò)展等,默認(rèn)是開啟狀態(tài)。
// 注冊
this.hook.add('addOrder', async function sendEmail(orderId) {
// 業(yè)務(wù)邏輯
});
this.hook.add('addOrder', async function addLog(orderId) {
// 業(yè)務(wù)邏輯
});
// 運(yùn)行 - 等待
await this.hook.run('addOrder', 1);
// 運(yùn)行 - 不等待
this.hook.run('addOrder', 1);
前后置
Doodoo.js登錄授權(quán)驗(yàn)證如果是異步的,可以放到前后置操作里面。如果前后置操作輸出數(shù)據(jù)到頁面的話,響應(yīng)將會中斷不會再繼續(xù)執(zhí)行下面的流程。
// app/demo/controller/home/index.js
module.exports = class extends doodoo.Controller {
async _initialize() {
// 控制器初始化
}
async _before() {
// 控制器前置
}
async _before_index() {
// 方法前置
}
async index() {
this.view('Hello World!');
}
async _after_index() {
// 方法后置
}
async _after() {
// 控制器后置
}
}
模型
Doodoo.js默認(rèn)使用了bookshelf??梢酝ㄟ^中間件的方式,自定義支持mysql,mongodb等等各種數(shù)據(jù)庫。
中間件
中間件
例如使用 koa-cors中間件,跟使用koa.js中間件一樣。
const Doodoo = require("doodoo.js");
const cors = require("koa-cors");
const app = new Doodoo();
app.use(cors());
app.start();
常用中間件
線上部署
正式環(huán)境推薦使用 pm2 啟動項(xiàng)目,配置參考
{
"name": "doodoo.js-demo",
"script": "app.js",
"watch": false,
"ignore_watch": [
"www/public",
"logs",
"node_modules"
],
"exec_mode": "cluster",
"max_memory_restart": "1G",
"error_file": "./logs/error.log",
"out_file": "./logs/out.log",
"node_args": [],
"args": [],
"env": {}
}
API
app
Doodoo實(shí)例
const Doodoo = require("doodoo.js");
const app = new Doodoo();
獲取socket
const Doodoo = require("doodoo.js");
const socket = require("socket.io");
const app = new Doodoo();
(async () => {
const server = app.start();
const io = socket(server);
})
如發(fā)現(xiàn)文檔中的錯(cuò)誤,請點(diǎn)擊這里聯(lián)系作者。
