媒介
這段時刻,用Eggjs作為後端效勞框架開闢了幾個項目。項目都很小,但為了進一步相識Eggjs,特地挑選了Eggjs作為框架基礎開闢後端效勞。時期也碰到過一些題目和坑,另有幾個值得注重的點,下面來說一下我這段時刻開闢的總結。
Egg.js 為企業級框架和運用而生 ,我們願望由 Egg.js 孕育出更多上層框架,協助開闢團隊和開闢人員下降開闢和保護本錢。
這個是Eggjs文檔對Eggjs的詮釋,關於Eggjs的細緻引見和運用請點解前面的地點;相關於Egg.js 1.x版本的文檔,已經有很大的改進了,許多癥結的處所都能夠比較完全解說和帶有代表性的實例。
步驟
最先
用的Egg.js版本是2.2.1
,對環境有肯定的要求,本人用的設置以下:
- 操縱系統:macOS
- 運轉環境:
v9.8.0
運用腳手架疾速豎立項目:
$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i
項目裝置終了,啟動項目:
$ npm run dev
$ open localhost:7001
至此,項目順遂豎立及啟動終了。
項目構造:(摘自文檔)
egg-project
├── package.json
├── app.js (可選)
├── agent.js (可選)
├── app
| ├── router.js
│ ├── controller
│ | └── home.js
│ ├── service (可選)
│ | └── user.js
│ ├── middleware (可選)
│ | └── response_time.js
│ ├── schedule (可選)
│ | └── my_task.js
│ ├── public (可選)
│ | └── reset.css
│ ├── view (可選)
│ | └── home.tpl
│ └── extend (可選)
│ ├── helper.js (可選)
│ ├── request.js (可選)
│ ├── response.js (可選)
│ ├── context.js (可選)
│ ├── application.js (可選)
│ └── agent.js (可選)
├── config
| ├── plugin.js
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js (可選)
| ├── config.local.js (可選)
| └── config.unittest.js (可選)
└── test
├── middleware
| └── response_time.test.js
└── controller
└── home.test.js
上述目次也是一個給開闢者一個目次豎立的指南,但依據文檔豎立的項目目次構造沒有那末全,基礎上標註為“可選”的都是初始沒有的,在/config
目次里也只要plugin.js
和config.default.js
兩個文件,其他文件要本身依據需求豎立。
豎立掌握器Controller
初始項目里會有一個示例Controller
,在豎立一個新的Controller
能夠參考/app/controller/home.js
的示例,一般而言,引薦運用module.exports
暴露出一個類或許參數為app
返回一個類的函數(文檔示例中為箭頭函數,其他體式格局沒試過不清楚),類內里包含着這塊營業的一些操縱,下面在掌握器文件目次/app/controller/
里新建一個文件名為user.js
的掌握器文件:
// 繼續egg的掌握器
const Controller = require('egg').Controller;
class UserController extends Controller {
async index() {
const { ctx } = this;
const { name } = ctx.request.body;
ctx.body = `hi, ${name}`;
}
async getUserById() {
const { userId } = this.ctx.request.body;
// 運用營業函數查詢用戶信息
const userInfo = await this.service.user.findById(userId);
this.ctx.body = {
msgCode: 0,
message: '勝利',
data: userInfo
};
}
}
// 注重:肯定要將掌握器暴露出去,不然要求的時刻會報找不到該controller的毛病;
module.exports = UserController;
增加路由
路由代碼在/app
目次之下,文件名router.js
,增加路由的代碼以下:
// 參數app為全局運用的對象
module.exports = app => {
const { router, controller, middleware } = app;
// 在這裏controller相稱於app下的controller文件目次,user為user.js,index為掌握器類的index要領
router.get('/', controller.user.index);
};
編寫營業
一般,controller
重要處置懲罰數據的構造和處置懲罰返回的效果,詳細的觸及的營業由service
營業類要領完成,編寫service
,在目次/app/service/
下豎立user.js
文件,並編寫代碼:
// 一樣要繼續egg的Service類
const Service = require('egg').Service;
class UserService extends Service {
// 依據用戶id查找用戶
async findUserById(id) {
const mysql = this.app.mysql;
const result = await mysql.get('users', { id });
return result;
}
}
module.exports = UserService;
增加插件
eggjs simple 版本旨在依據營業需求增加eggjs的插件來搭建上層框架。在本人開闢過程當中,用到的一些插件做扼要申明。
插件裝置:
$ npm i --save egg-pluginName
在文件/config/plugin.js
增加設置:
exports.pluginName = {
enable: true,
package: 'egg-pluginName',
};
須要插件初始化設置的情況下,修正/config/config.default.js
:
config.pluginName = {
// 設置項
};
egg-mysql
因運用
mysql
數據庫,須要一個nodejs
對mysql
的操縱庫,基於eggjs
挑選了egg-mysql
,操縱文檔點擊這裏。基礎的數據庫增刪查改都能操縱,寫起來還挺輕易,但是有個需求,編寫某個接口,返回當前用戶某一段時刻的數據,這就比較蛋疼了,找了良久,就連egg-mysql
封裝的庫ali-rds檢察了源碼也找不到這類要領,無法之下,只能經由過程構造原生的mysql
查詢語句去動態拼集,雖然不引薦,不過假如找到更好的要領,照樣情願改寫的。查詢指定日期數據的
mysql
相干參考材料:
egg-redis
redis
相稱於基於內存的一個微型數據庫,其存取速率異常快,代碼實行的時刻險些覺得不到壅塞,這裏運用
egg-redis
作為項目對
redis
的操縱庫,文檔點擊
這裏。文檔申明剖析了基礎的存取和設置操縱,關於較為龐雜的操縱只能經由過程檢察
redis
的
官方文檔,對應的敕令小寫即為要領名:
redis
敕令
DECR
,對應的要領運用:
// /app/controller/user.js
const value = await this.app.redis.decr(keyName);
擴大內置對象
增加過幾個插件以後,發明个中的源代碼都是以擴大內置對象的體式格局去掛載相干的庫或許插件的。
ctx
文檔原文:“一般來說屬性的盤算在統一次要求中只須要舉行一次,那末肯定要完成緩存,不然在統一次要求中屢次接見屬性時會盤算屢次,如許會下降運用機能。
引薦的體式格局是運用 Symbol + Getter 的形式。”
const jwt = require('jsonwebtoken');
const JWT = Symbol('Context#jwt');
module.exports = {
get jwt() {
if (!this[JWT]) {
this[JWT] = jwt;
}
return this[JWT];
}
};
第一次擴大cxt
對象的時刻,不明白為什麼要運用Symbol + Getter
的形式,厥後基於這個題目,查找材料,發明這類體式格局更能防止和其他屬性名發生衝突,上述代碼中,ctx
的jwt
定義為只讀體式格局。在輕易保護的同時,天生一個帶有定名空間(Context#jwt
)字符串形貌的Symbol
實例數據, 作為ctx
的屬性,經由過程只讀屬性jwt
來獵取內部的JWT
屬性。
PS:
ctx.JWT == ctx[JWT] // false
關於Symbol
的引見和運用請參考阮一峰的ES6。
app
在擴大app
對象的時刻,碰到個題目,就是假如須要獵取ctx
怎麼辦?
查找文檔,找到了在擴大app
對象時,只須要在函數體里增加一句代碼:
const ctx = app.createAnonymousContext();
就能夠獵取ctx
對象,這關於運用其他函數供應了一道橋樑。
編寫中間件
eggjs
的中間件處置懲罰流程遵照
koa
的洋蔥式要求模子中間件的寫法:
module.exports = options => {
return async function middleWareFunctionName (ctx, next) {
// 掌握器之前營業處置懲罰代碼
// ...
await next();
//掌握器以後營業處置懲罰代碼
// ...
}
}
中間件以返回一個處置懲罰營業的函數為主體,函數吸收兩個參數:ctx
、next
。ctx
則是要求級別的對象,next()
要領能夠讓要求進入下一個步驟。特別注重的是:在一個掌握器中,有對要求抵達下一步之前做一些操縱的,能夠掌握next()
在代碼流程中的位置,厥後也能夠處置懲罰要求以後的操縱。
制訂定時使命
在eggjs
寫定時使命也是異常簡樸的,關注於營業代碼,加以簡樸的設置,即可運用定時使命。
下面是一個簡樸的定時統計營業數據的定時使命:
const Subscription = require('egg').Subscription;
class Statistics extends Subscription {
// 經由過程 schedule 屬性來設置定時使命的實行距離等設置
static get schedule() {
return {
cron: '00 59 23 * * *', // 秒 分 時 日 月 年
// interval: '10s', // 設置時刻距離觸發,單元s為秒,ms為毫秒
type: 'worker', // all 指定一切的 worker 都須要實行, worker 為某一個 worker 實行
};
}
// subscribe 是真正定時使命實行時被運轉的函數
async subscribe() {
// 定時使命營業代碼
// ...
}
}
module.exports = Statistics;
在順序啟動的時刻,就會在設置的指定機遇實行相干的營業代碼。
設置(待補充)
csrf的議論
eggjs
在v2.x
版本以後默許開啟了csrf
插件,已確保基於cookie
存儲考證信息的網站信息平安。
csrf
能將要求限定在同源網站,即只要具有“專有令牌”的網站發送要求才會準確相應。此處容易與jwt
的作用殽雜,能夠看看這篇文章。
跨域
運用
egg-cors
;
前後端星散用戶考證
運用jwt考證
jwt
則在認證體式格局上跟csrf
上有所不同,jwt
能夠在不運用cookie
的情況下,以token
的體式格局在前後端交互數據的body
里傳輸,也能夠在header
里設置相干信息,細緻能夠參考這篇文章。
日記(待補充)
類的寫法
長途機開闢布置
文檔中,有《運用布置》一文,內里引見的很細緻的。運用
egg-script
插件啟動臨盆環境中的運用順序。項目臨盆寂靜布置,啟動運用
npm start
,住手運用
npm stop
。
另,在開闢環境里想要運用pm2治理歷程背景啟動,--watch
會不停打印掌握台日記,緣由不清楚。
臨盆環境布置
啟動敕令:
$ npm install --production
$ npm start
住手敕令:
$ npm stop
總結
長處
運用
eggjs
開闢企業級運用照樣相稱輕易的,雖然說要依據需求裝,但裝置和設置步驟異常簡樸,許多有效的營業設置都能夠很輕易疾速設置好,還能夠辨別環境,項目構造和挪用體式格局很合理。
不足
東西函數的接見須要本身手動增加擴大
另
沒有寫測試,願望下次補上。