回顧一下上篇講到的內容,上篇講了:javascript
Service 就是在複雜業務場景下用於作業務邏輯封裝的一個抽象層html
// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async find(uid) {
const user = await this.ctx.db.query('select * from user where uid = ?', uid);
return user;
}
}
module.exports = UserService;
複製代碼
每一次用戶請求,框架都會實例化對應的 Service 實例,因爲它繼承於 egg.Service
,故擁有下列屬性方便咱們進行開發:java
this.ctx
: 當前請求的上下文 Context 對象的實例this.app
: 當前應用 Application 對象的實例this.service
:應用定義的 Servicethis.config
:應用運行時的配置項this.logger
:logger 對象,上面有四個方法(debug
,info
,warn
,error
),分別表明打印四個不一樣級別的日誌,使用方法和效果與 context logger 中介紹的同樣,可是經過這個 logger 對象記錄的日誌,在日誌前面會加上打印該日誌的文件路徑,以便快速定位日誌打印位置。this.ctx.curl
發起網絡調用。this.ctx.service.otherService
調用其餘 Service。this.ctx.db
發起數據庫調用等, db 多是其餘插件提早掛載到 app 上的模塊。app/service
目錄,能夠支持多級目錄,訪問的時候能夠經過目錄名級聯訪問。app/service/biz/user.js => ctx.service.biz.user // 多級目錄,依據目錄名級聯訪問
app/service/sync_user.js => ctx.service.syncUser // 下劃線自動轉換爲自動駝峯
app/service/HackerNews.js => ctx.service.hackerNews // 大寫自動轉換爲駝峯
複製代碼
ctx.service.xx
時延遲實例化,因此 Service 中能夠經過 this.ctx 獲取到當前請求的上下文。// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
async info() {
const { ctx } = this;
const userId = ctx.params.id;
const userInfo = await ctx.service.user.find(userId);
ctx.body = userInfo;
}
}
module.exports = UserController;
// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
// 默認不須要提供構造函數。
// constructor(ctx) {
// super(ctx); 若是須要在構造函數作一些處理,必定要有這句話,才能保證後面 `this.ctx`的使用。
// // 就能夠直接經過 this.ctx 獲取 ctx 了
// // 還能夠直接經過 this.app 獲取 app 了
// }
async find(uid) {
// 假如 咱們拿到用戶 id 從數據庫獲取用戶詳細信息
const user = await this.ctx.db.query('select * from user where uid = ?', uid);
// 假定這裏還有一些複雜的計算,而後返回須要的信息。
const picture = await this.getPicture(uid);
return {
name: user.user_name,
age: user.age,
picture,
};
}
async getPicture(uid) {
const result = await this.ctx.curl(`http://photoserver/uid=${uid}`, { dataType: 'json' });
return result.data;
}
}
module.exports = UserService;
複製代碼
在使用 Koa 中間件過程當中發現了下面一些問題:node
一個插件其實就是一個『迷你的應用』,和應用(app)幾乎同樣:+mysql
plugin.js
,只能聲明跟其餘插件的依賴,而不能決定其餘插件的開啓與否。他們的關係是:linux
插件通常經過 npm 模塊的方式進行復用:git
npm i egg-mysql --save
複製代碼
建議經過 ^ 的方式引入依賴,而且強烈不建議鎖定版本。github
{
"dependencies": {
"egg-mysql": "^3.0.0"
}
}
複製代碼
而後須要在應用或框架的 config/plugin.js
中聲明:web
// config/plugin.js
// 使用 mysql 插件
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
複製代碼
就能夠直接使用插件提供的功能:app.mysql.query(sql, values);
sql
egg-mysql 插件文檔
plugin.js
中的每一個配置項支持:
{Boolean} enable
- 是否開啓此插件,默認爲 true{String} package
- npm 模塊名稱,經過 npm 模塊形式引入插件{String} path
- 插件絕對路徑,跟 package 配置互斥{Array} env
- 只有在指定運行環境才能開啓,會覆蓋插件自身 package.json 中的配置在上層框架內部內置的插件,應用在使用時就不用配置 package 或者 path,只須要指定 enable 與否:
// 對於內置插件,能夠用下面的簡潔方式開啓或關閉
exports.onerror = false;
複製代碼
同時,咱們還支持 plugin.{env}.js
這種模式,會根據運行環境加載插件配置。
好比定義了一個開發環境使用的插件 egg-dev
,只但願在本地環境加載,能夠安裝到 devDependencies
。
// npm i egg-dev --save-dev
// package.json
{
"devDependencies": {
"egg-dev": "*"
}
}
複製代碼
而後在 plugin.local.js
中聲明:
// config/plugin.local.js
exports.dev = {
enable: true,
package: 'egg-dev',
};
複製代碼
這樣在生產環境能夠 npm i --production
不須要下載 egg-dev
的包了。
注意:
plugin.default.js
package
是 npm
方式引入,也是最多見的引入方式path
是絕對路徑引入,如應用內部抽了一個插件,但還沒達到開源發佈獨立 npm 的階段,或者是應用本身覆蓋了框架的一些插件// config/plugin.js
const path = require('path');
exports.mysql = {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-mysql'),
};
複製代碼
插件通常會包含本身的默認配置,應用開發者能夠在 config.default.js
覆蓋對應的配置:
// config/config.default.js
exports.mysql = {
client: {
host: 'mysql.com',
port: '3306',
user: 'test_user',
password: 'test_password',
database: 'test',
},
};
複製代碼
框架默認內置了企業級應用經常使用的插件:
onerror
統一異常處理Session
Session 實現更多社區的插件能夠 GitHub 搜索 egg-plugin
。
插件開發詳情見 插件開發
雖然咱們經過框架開發的 HTTP Server 是請求響應模型的,可是仍然還會有許多場景須要執行一些定時任務
全部的定時任務都統一存放在 app/schedule
目錄下,每個文件都是一個獨立的定時任務,能夠配置定時任務的屬性和要執行的方法。
在 app/schedule
目錄下建立一個 update_cache.js
文件
const Subscription = require('egg').Subscription;
class UpdateCache extends Subscription {
// 經過 schedule 屬性來設置定時任務的執行間隔等配置
static get schedule() {
return {
interval: '1m', // 1 分鐘間隔
type: 'all', // 指定全部的 worker 都須要執行
};
}
// subscribe 是真正定時任務執行時被運行的函數
async subscribe() {
const res = await this.ctx.curl('http://www.api.com/cache', {
dataType: 'json',
});
this.ctx.app.cache = res.data;
}
}
module.exports = UpdateCache;
複製代碼
還能夠簡寫爲
module.exports = {
schedule: {
interval: '1m', // 1 分鐘間隔
type: 'all', // 指定全部的 worker 都須要執行
},
async task(ctx) {
const res = await ctx.curl('http://www.api.com/cache', {
dataType: 'json',
});
ctx.app.cache = res.data;
},
};
複製代碼
這個定時任務會在每個 Worker 進程上每 1 分鐘執行一次,將遠程數據請求回來掛載到 app.cache
上。
task
或 subscribe
同時支持 generator functio
和 async function
。task
的入參爲 ctx
,匿名的 Context 實例,能夠經過它調用 service
等。定時任務能夠指定 interval 或者 cron 兩種不一樣的定時方式。
經過 schedule.interval
參數來配置定時任務的執行時機,定時任務將會每間隔指定的時間執行一次。interval 能夠配置成
module.exports = {
schedule: {
// 每 10 秒執行一次
interval: '10s',
},
};
複製代碼
經過 schedule.cron 參數來配置定時任務的執行時機,定時任務將會按照 cron 表達式在特定的時間點執行。cron 表達式經過 cron-parser
進行解析。
注意:cron-parser 支持可選的秒(linux crontab 不支持)。
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)
複製代碼
module.exports = {
schedule: {
// 每三小時準點執行一次
cron: '0 0 */3 * * *',
},
};
複製代碼
worker 和 all。worker 和 all 都支持上面的兩種定時方式,只是當到執行時機時,會執行定時任務的 worker 不一樣:
worker
類型:每臺機器上只有一個 worker 會執行這個定時任務,每次執行定時任務的 worker 的選擇是隨機的。all
類型:每臺機器上的每一個 worker 都會執行這個定時任務。除了剛纔介紹到的幾個參數以外,定時任務還支持這些參數:
cronOptions
: 配置 cron 的時區等,參見 cron-parser 文檔immediate
:配置了該參數爲 true 時,這個定時任務會在應用啓動並 ready 後馬上執行一次這個定時任務。disable
:配置該參數爲 true 時,這個定時任務不會被啓動。env
:數組,僅在指定的環境下才啓動該定時任務。執行日誌會輸出到 ${appInfo.root}/logs/{app_name}/egg-schedule.log
,默認不會輸出到控制檯,能夠經過 config.customLogger.scheduleLogger
來自定義。
// config/config.default.js
config.customLogger = {
scheduleLogger: {
// consoleLevel: 'NONE',
// file: path.join(appInfo.root, 'logs', appInfo.name, 'egg-schedule.log'),
},
};
複製代碼
module.exports = app => {
return {
schedule: {
interval: app.config.cacheTick,
type: 'all',
},
async task(ctx) {
const res = await ctx.curl('http://www.api.com/cache', {
contentType: 'json',
});
ctx.app.cache = res.data;
},
};
};
複製代碼
咱們能夠經過 app.runSchedule(schedulePath)
來運行一個定時任務。app.runSchedule
接受一個定時任務文件路徑(app/schedule
目錄下的相對路徑或者完整的絕對路徑),執行對應的定時任務,返回一個 Promise。
const mm = require('egg-mock');
const assert = require('assert');
it('should schedule work fine', async () => {
const app = mm.app();
await app.ready();
await app.runSchedule('update_cache');
assert(app.cache);
});
複製代碼
app.js
中編寫初始化邏輯。module.exports = app => {
app.beforeStart(async () => {
// 保證應用啓動監聽端口前數據已經準備好了
// 後續數據的更新由定時任務自動觸發
await app.runSchedule('update_cache');
});
};
複製代碼
框架提供了多種擴展點擴展自身的功能:Application、Context、Request、Response、Helper。
ctx.app
this.app
訪問到 Application 對象,例如this.app.config
訪問配置對象。// app.js
module.exports = app => {
// 使用 app 對象
};
複製代碼
框架會把 app/extend/application.js
中定義的對象與 Koa Application 的 prototype 對象進行合併,在應用啓動時會基於擴展後的 prototype 生成 app 對象。
// app/extend/application.js
module.exports = {
foo(param) {
// this 就是 app 對象,在其中能夠調用 app 上的其餘方法,或訪問屬性
},
};
複製代碼
屬性擴展
通常來講屬性的計算只須要進行一次,那麼必定要實現緩存,不然在屢次訪問屬性時會計算屢次,這樣會下降應用性能。
推薦的方式是使用 Symbol + Getter 的模式。
// app/extend/application.js
const BAR = Symbol('Application#bar');
module.exports = {
get bar() {
// this 就是 app 對象,在其中能夠調用 app 上的其餘方法,或訪問屬性
if (!this[BAR]) {
// 實際狀況確定更復雜
this[BAR] = this.config.xx + this.config.yy;
}
return this[BAR];
},
};
複製代碼
Context 指的是 Koa 的請求上下文,這是 請求級別 的對象,每次請求生成一個 Context 實例,一般咱們也簡寫成 ctx。在全部的文檔中,Context 和 ctx 都是指 Koa 的上下文對象。
ctx.cookies.get('foo')
。this.ctx
,方法的寫法直接經過 ctx
入參。this.ctx.cookies.get('foo')
。框架會把 app/extend/context.js
中定義的對象與 Koa Context 的 prototype 對象進行合併,在處理請求時會基於擴展後的 prototype 生成 ctx 對象。
// app/extend/context.js
module.exports = {
foo(param) {
// this 就是 ctx 對象,在其中能夠調用 ctx 上的其餘方法,或訪問屬性
},
};
複製代碼
屬性擴展同 Application
Request 對象和 Koa 的 Request 對象相同,是 請求級別 的對象,它提供了大量請求相關的屬性和方法供使用。
ctx.request
ctx
上的不少屬性和方法都被代理到 request
對象上,對於這些屬性和方法使用 ctx
和使用 request
去訪問它們是等價的,例如 ctx.url === ctx.request.url
。
框架會把 app/extend/request.js
中定義的對象與內置 request
的 prototype 對象進行合併,在處理請求時會基於擴展後的 prototype 生成 request
對象。
// app/extend/request.js
module.exports = {
get foo() {
return this.get('x-request-foo');
},
};
複製代碼
Response 對象和 Koa 的 Response 對象相同,是 請求級別 的對象,它提供了大量響應相關的屬性和方法供使用。
ctx.response
ctx
上的不少屬性和方法都被代理到 response
對象上,對於這些屬性和方法使用 ctx
和使用 response
去訪問它們是等價的,例如 ctx.status = 404
和 ctx.response.status = 404
是等價的。
框架會把 app/extend/response.js
中定義的對象與內置 response
的 prototype 對象進行合併,在處理請求時會基於擴展後的 prototype 生成 response 對象。
// app/extend/response.js
module.exports = {
set foo(value) {
this.set('x-response-foo', value);
},
};
複製代碼
就能夠這樣使用啦:this.response.foo = 'bar';
Helper 函數用來提供一些實用的 utility 函數。
它的做用在於咱們能夠將一些經常使用的動做抽離在 helper.js 裏面成爲一個獨立的函數,這樣能夠用 JavaScript 來寫複雜的邏輯,避免邏輯分散各處。另外還有一個好處是 Helper 這樣一個簡單的函數,可讓咱們更容易編寫測試用例。
框架內置了一些經常使用的 Helper 函數。咱們也能夠編寫自定義的 Helper 函數。
經過 ctx.helper
訪問到 helper 對象,例如:
// 假設在 app/router.js 中定義了 home router
app.get('home', '/', 'home.index');
// 使用 helper 計算指定 url path
ctx.helper.pathFor('home', { by: 'recent', limit: 20 })
// => /?by=recent&limit=20
複製代碼
框架會把 app/extend/helper.js
中定義的對象與內置 helper
的 prototype 對象進行合併,在處理請求時會基於擴展後的 prototype 生成 helper
對象。
// app/extend/helper.js
module.exports = {
foo(param) {
// this 是 helper 對象,在其中能夠調用其餘 helper 方法
// this.ctx => context 對象
// this.app => application 對象
},
};
複製代碼
框架提供了統一的入口文件(app.js
)進行啓動過程自定義,這個文件返回一個 Boot 類,咱們能夠經過定義 Boot 類中的生命週期方法來執行啓動應用過程當中的初始化工做。
框架提供了這些生命週期函數供開發人員處理:
configWillLoad
)configDidLoad
)didLoad
)willReady
)didReady
)serverDidReady
)beforeClose
)// app.js
class AppBootHook {
constructor(app) {
this.app = app;
}
configWillLoad() {
// 此時 config 文件已經被讀取併合並,可是還並未生效
// 這是應用層修改配置的最後時機
// 注意:此函數只支持同步調用
// 例如:參數中的密碼是加密的,在此處進行解密
this.app.config.mysql.password = decrypt(this.app.config.mysql.password);
// 例如:插入一箇中間件到框架的 coreMiddleware 之間
const statusIdx = this.app.config.coreMiddleware.indexOf('status');
this.app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit');
}
async didLoad() {
// 全部的配置已經加載完畢
// 能夠用來加載應用自定義的文件,啓動自定義的服務
// 例如:建立自定義應用的示例
this.app.queue = new Queue(this.app.config.queue);
await this.app.queue.init();
// 例如:加載自定義的目錄
this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {
fieldClass: 'tasksClasses',
});
}
async willReady() {
// 全部的插件都已啓動完畢,可是應用總體還未 ready
// 能夠作一些數據初始化等操做,這些操做成功纔會啓動應用
// 例如:從數據庫加載數據到內存緩存
this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);
}
async didReady() {
// 應用已經啓動完畢
const ctx = await this.app.createAnonymousContext();
await ctx.service.Biz.request();
}
async serverDidReady() {
// http / https server 已啓動,開始接受外部請求
// 此時能夠從 app.server 拿到 server 的實例
this.app.server.on('timeout', socket => {
// handle socket timeout
});
}
}
module.exports = AppBootHook;
複製代碼
在本地開發時,咱們使用 egg-bin dev 來啓動服務,可是在部署應用的時候不能夠這樣使用。由於 egg-bin dev 會針對本地開發作不少處理,而生產運行須要一個更加簡單穩定的方式。
服務器須要預裝 Node.js,框架支持的 Node 版本爲 >= 8.0.0
。
框架內置了 egg-cluster 來啓動 Master 進程,Master 有足夠的穩定性,再也不須要使用 pm2 等進程守護模塊。
同時,框架也提供了 egg-scripts 來支持線上環境的運行和中止。
npm i egg-scripts --save
複製代碼
{
"scripts": {
"start": "egg-scripts start --daemon",
"stop": "egg-scripts stop"
}
}
複製代碼
這樣咱們就能夠經過 npm start 和 npm stop 命令啓動或中止應用。
egg-scripts start --port=7001 --daemon --title=egg-server-showcase
複製代碼
支持如下參數:
--port=7001
端口號,默認會讀取環境變量 process.env.PORT
,如未傳遞將使用框架內置端口 7001。--daemon
是否容許在後臺模式,無需 nohup
。若使用 Docker 建議直接前臺運行。--env=prod
框架運行環境,默認會讀取環境變量 process.env.EGG_SERVER_ENV
, 如未傳遞將使用框架內置環境 prod。--workers=2
框架 worker 線程數,默認會建立和 CPU 核數至關的 app worker 數,能夠充分的利用 CPU 資源。--title=egg-server-showcase
用於方便 ps 進程時 grep 用,默認爲 egg-server-${appname}
。--framework=yadan
若是應用使用了自定義框架,能夠配置 package.json 的 egg.framework 或指定該參數。--ignore-stderr
忽略啓動期的報錯。--https.key
指定 HTTPS 所需密鑰文件的完整路徑。--https.cert
指定 HTTPS 所需證書文件的完整路徑。更多參數可查看 egg-scripts 和 egg-cluster 文檔。
啓動配置項
// config/config.default.js
exports.cluster = {
listen: {
port: 7001,
hostname: '127.0.0.1',
// path: '/var/run/egg.sock',
}
}
複製代碼
egg-scripts stop [--title=egg-server]
複製代碼
該命令將殺死 master 進程,並通知 worker 和 agent 優雅退出。
--title=egg-server
用於殺死指定的 egg 應用,未傳遞則會終止全部的 Egg 應用。
框架內置了強大的企業級日誌支持,由 egg-logger 模塊提供。
${appInfo.root}/logs/${appInfo.name}
路徑下,例如 /home/admin/logs/example-app
。/path/to/example-app/logs/example-app
。若是想自定義日誌路徑:
// config/config.${env}.js
exports.logger = {
dir: '/path/to/your/custom/log/dir',
};
複製代碼
框架內置了幾種日誌,分別在不一樣的場景下使用:
${appInfo.name}-web.log
,例如 example-app-web.log
,應用相關日誌,供應用開發者使用的日誌。咱們在絕大數狀況下都在使用它。egg-web.log
框架內核、插件日誌。common-error.log
實際通常不會直接使用它,任何 logger 的 .error() 調用輸出的日誌都會重定向到這裏,重點經過查看此日誌定位異常。egg-agent.log
agent 進程日誌,框架和使用到 agent 進程執行任務的插件會打印一些日誌到這裏。若是想自定義以上日誌文件名稱,能夠在 config 文件中覆蓋默認值:
// config/config.${env}.js
module.exports = appInfo => {
return {
logger: {
appLogName: `${appInfo.name}-web.log`,
coreLogName: 'egg-web.log',
agentLogName: 'egg-agent.log',
errorLogName: 'common-error.log',
},
};
};
複製代碼
用於記錄 Web 行爲相關的日誌。
每行日誌會自動記錄上當前請求的一些基本信息, 如 [$userId/$ip/$traceId/${cost}ms $method $url]
。
ctx.logger.debug('debug info');
ctx.logger.info('some request data: %j', ctx.request.body);
ctx.logger.warn('WARNNING!!!!');
// 錯誤日誌記錄,直接會將錯誤日誌完整堆棧信息記錄下來,而且輸出到 errorLog 中
// 爲了保證異常可追蹤,必須保證全部拋出的異常都是 Error 類型,由於只有 Error 類型纔會帶上堆棧信息,定位到問題。
ctx.logger.error(new Error('whoops'));
複製代碼
對於框架開發者和插件開發者會使用到的 Context Logger
還有 ctx.coreLogger
。
若是咱們想作一些應用級別的日誌記錄,如記錄啓動階段的一些數據信息,能夠經過 App Logger 來完成。
// app.js
module.exports = app => {
app.logger.debug('debug info');
app.logger.info('啓動耗時 %d ms', Date.now() - start);
app.logger.warn('warning!');
app.logger.error(someErrorObj);
};
複製代碼
對於框架和插件開發者會使用到的 App Logger 還有 app.coreLogger
。
// app.js
module.exports = app => {
app.coreLogger.info('啓動耗時 %d ms', Date.now() - start);
};
複製代碼
在開發框架和插件時有時會須要在 Agent 進程運行代碼,這時使用 agent.coreLogger
。
// agent.js
module.exports = agent => {
agent.logger.debug('debug info');
agent.logger.info('啓動耗時 %d ms', Date.now() - start);
agent.logger.warn('warning!');
agent.logger.error(someErrorObj);
};
複製代碼
默認編碼爲 utf-8
,可經過以下方式覆蓋:
// config/config.${env}.js
exports.logger = {
encoding: 'gbk',
};
複製代碼
// config/config.${env}.js
exports.logger = {
outputJSON: true,
};
複製代碼
日誌分爲 NONE
,DEBUG
,INFO
,WARN
和 ERROR
5 個級別。
日誌打印到文件中的同時,爲了方便開發,也會同時打印到終端中。
默認只會輸出 INFO
及以上(WARN
和 ERROR
)的日誌到文件中。
打印全部級別日誌到文件中:
// config/config.${env}.js
exports.logger = {
level: 'DEBUG',
};
複製代碼
關閉全部打印到文件的日誌:
// config/config.${env}.js
exports.logger = {
level: 'NONE',
};
複製代碼
生產環境打印 debug 日誌
爲了不一些插件的調試日誌在生產環境打印致使性能問題,生產環境默認禁止打印 DEBUG 級別的日誌,若是確實有需求在生產環境打印 DEBUG 日誌進行調試,須要打開 allowDebugAtProd
配置項。
// config/config.prod.js
exports.logger = {
level: 'DEBUG',
allowDebugAtProd: true,
};
複製代碼
默認只會輸出 INFO
及以上(WARN
和 ERROR
)的日誌到終端中。(注意:這些日誌默認只在 local 和 unittest 環境下會打印到終端)
logger.consoleLevel
: 輸出到終端日誌的級別,默認爲 INFO
打印全部級別日誌到終端:
// config/config.${env}.js
exports.logger = {
consoleLevel: 'DEBUG',
};
複製代碼
關閉全部打印到終端的日誌:
// config/config.${env}.js
exports.logger = {
consoleLevel: 'NONE',
};
複製代碼
基於性能的考慮,在正式環境下,默認會關閉終端日誌輸出。若有須要,你能夠經過下面的配置開啓。(不推薦)
// config/config.${env}.js
exports.logger = {
disableConsoleAfterReady: false,
};
複製代碼
框架對日誌切割的支持由 egg-logrotator 插件提供。
這是框架的默認日誌切割方式,在每日 00:00
按照 .log.YYYY-MM-DD
文件名進行切割。
以 appLog 爲例,當前寫入的日誌爲 example-app-web.log
,當凌晨 00:00 時,會對日誌進行切割,把過去一天的日誌按 example-app-web.log.YYYY-MM-DD
的形式切割爲單獨的文件。
// config/config.${env}.js
const path = require('path');
module.exports = appInfo => {
return {
logrotator: {
filesRotateBySize: [
path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log'),
],
maxFileSize: 2 * 1024 * 1024 * 1024,
},
};
};
複製代碼
這和默認的按天切割很是相似,只是時間縮短到每小時。
// config/config.${env}.js
const path = require('path');
module.exports = appInfo => {
return {
logrotator: {
filesRotateByHour: [
path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),
],
},
};
};
複製代碼
一般 Web 訪問是高頻訪問,每次打印日誌都寫磁盤會形成頻繁磁盤 IO,爲了提升性能,咱們採用的文件日誌寫入策略是:
日誌同步寫入內存,異步每隔一段時間(默認 1 秒)刷盤
更多詳細請參考 egg-logger 和 egg-logrotator。
框架基於 urllib 內置實現了一個 HttpClient,應用能夠很是便捷地完成任何 HTTP 請求。
架在應用初始化的時候,會自動將 HttpClient 初始化到 app.httpclient
。 同時增長了一個 app.curl(url, options)
方法,它等價於 app.httpclient.request(url, options)
。
// app.js
module.exports = app => {
app.beforeStart(async () => {
// 示例:啓動的時候去讀取 https://registry.npm.taobao.org/egg/latest 的版本信息
const result = await app.curl('https://registry.npm.taobao.org/egg/latest', {
dataType: 'json',
});
app.logger.info('Egg latest version: %s', result.data.version);
});
};
複製代碼
框架在 Context 中一樣提供了 ctx.curl(url, options)
和 ctx.httpclient
,保持跟 app 下的使用體驗一致。 這樣就能夠在有 Context 的地方(如在 controller 中)很是方便地使用 ctx.curl()
方法完成一次 HTTP 請求。
// app/controller/npm.js
class NpmController extends Controller {
async index() {
const ctx = this.ctx;
// 示例:請求一個 npm 模塊信息
const result = await ctx.curl('https://registry.npm.taobao.org/egg/latest', {
// 自動解析 JSON response
dataType: 'json',
// 3 秒超時
timeout: 3000,
});
ctx.body = {
status: result.status,
headers: result.headers,
package: result.data,
};
}
}
複製代碼
// app/controller/npm.js
class NpmController extends Controller {
async get() {
const ctx = this.ctx;
const result = await ctx.curl('https://httpbin.org/get?foo=bar');
ctx.status = result.status;
ctx.set(result.headers);
ctx.body = result.data;
}
}
複製代碼
const result = await ctx.curl('https://httpbin.org/post', {
// 必須指定 method
method: 'POST',
// 經過 contentType 告訴 HttpClient 以 JSON 格式發送
contentType: 'json',
data: {
hello: 'world',
now: Date.now(),
},
// 明確告訴 HttpClient 以 JSON 格式處理返回的響應 body
dataType: 'json',
});
複製代碼
const result = await ctx.curl('https://httpbin.org/put', {
// 必須指定 method
method: 'PUT',
// 經過 contentType 告訴 HttpClient 以 JSON 格式發送
contentType: 'json',
data: {
update: 'foo bar',
},
// 明確告訴 HttpClient 以 JSON 格式處理響應 body
dataType: 'json',
});
複製代碼
const result = await ctx.curl('https://httpbin.org/delete', {
// 必須指定 method
method: 'DELETE',
// 明確告訴 HttpClient 以 JSON 格式處理響應 body
dataType: 'json',
});
複製代碼
httpclient.request(url, options)
HttpClient 默認全局配置,應用能夠經過 config/config.default.js 覆蓋此配置。
經常使用
data: Object
須要發送的請求數據,根據 method 自動選擇正確的數據處理方式。
GET
,HEAD
:經過 querystring.stringify(data)
處理後拼接到 url 的 query 參數上。POST
,PUT
和 DELETE
等:須要根據 contentType
作進一步判斷處理。
contentType = json
:經過 JSON.stringify(data)
處理,並設置爲 body 發送。querystring.stringify(data)
處理,並設置爲 body 發送。files: Mixed
method: String
設置請求方法,默認是 GET。 支持 GET、POST、PUT、DELETE、PATCH 等全部 HTTP 方法。contentType: String
設置請求數據格式,默認是 undefined,HttpClient 會自動根據 data 和 content 參數自動設置。 data 是 object 的時候默認設置的是 form。支持 json 格式。dataType: String
設置響應數據格式,默認不對響應數據作任何處理,直接返回原始的 buffer 格式數據。 支持 text 和 json 兩種格式。headers: Object
自定義請求頭。timeout: Number|Array
請求超時時間,默認是 [ 5000, 5000 ]
,即建立鏈接超時是 5 秒,接收響應超時是 5 秒。若是你須要對 HttpClient 的請求進行抓包調試,能夠添加如下配置到 config.local.js
:
// config.local.js
module.exports = () => {
const config = {};
// add http_proxy to httpclient
if (process.env.http_proxy) {
config.httpclient = {
request: {
enableProxy: true,
rejectUnauthorized: false,
proxy: process.env.http_proxy,
},
};
}
return config;
}
複製代碼
而後啓動你的抓包工具,如 charles 或 fiddler。
最後經過如下指令啓動應用:
http_proxy=http://127.0.0.1:8888 npm run dev
複製代碼
windows 下能夠用cmder 或者 git bash
set http_proxy=http://127.0.0.1:8888 && npm run dev
複製代碼
而後就能夠正常操做了,全部通過 HttpClient 的請求,均可以你的抓包工具中查看到。