diff --git a/docs/source/en/basics/app-start.md b/docs/source/en/basics/app-start.md index 61112ae8c7..823d64a7f3 100644 --- a/docs/source/en/basics/app-start.md +++ b/docs/source/en/basics/app-start.md @@ -2,36 +2,79 @@ When the application starts up, we often need to set up some initialization logic. The application bootstraps with those specific configurations. It is in a healthy state and be able to take external service requests after those configurations successfully applied. Otherwise, it failed. -The framework starts with a file called `app.js` that executes the application initialization logic, if present, and it returns a function. -For example, we need to load a list of national cities from the remote server during application startup for subsequent use in the controller: +The framework provides a unified entry file (`app.js`) for boot process customization. This file need returns a Boot class. We can define the initialization process in the startup application by defining the lifecycle method in the Boot class. + +The framework has provided you several functions to handle during the whole [life cycle](../advanced/loader.md#life-cycles): + +- `configWillLoad`: All the config files are ready to load, so this is the LAST chance to modify them. +- `configDidLoad`: When all the config files have been loaded. +- `didLoad`: When all the files have been loaded. +- `willReady`: When all the plug-ins are ready. +- `didReady`: When all the workers are ready. +- `serverDidReady`: When the server is ready. +- `beforeClose`: Before the application is closed. + +We can defined Boot class in `app.js`. Below we take a few examples of lifecycle functions commonly used in application development: ```js // app.js -module.exports = app => { - app.beforeStart(async () => { - // The lifecycle method runs before the application bootstraps - app.cities = await app.curl('http://example.com/city.json', { - method: 'GET', - dataType: 'json', +class AppBootHook { + constructor(app) { + this.app = app; + } + + configWillLoad() { + // The config file has been read and merged, but it has not yet taken effect + // This is the last time the application layer modifies the configuration + // Note: This function only supports synchronous calls. + + // For example: the password in the parameter is encrypted, decrypt it here + this.app.config.mysql.password = decrypt(this.app.config.mysql.password); + // For example: insert a middleware into the framework's coreMiddleware + const statusIdx = app.config.coreMiddleware.indexOf('status'); + app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit'); + } + + async didLoad() { + // All configurations have been loaded +    // Can be used to load the application custom file, start a custom service + +    // Example: Creating a custom app example + this.app.queue = new Queue(this.app.config.queue); + await this.app.queue.init(); + + // For example: load a custom directory + app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', { + fieldClass: 'tasksClasses', }); + } - // also could create an anonymous context to call Service - // const ctx = app.createAnonymousContext(); - // app.cities = await ctx.service.cities.load(); - }); -}; -``` + async willReady() { + // All plugins have been started, but the application is not yet ready +    // Can do some data initialization and other operations + // Application will start after these operations executed succcessfully -`cities` attribute has attached on the global `app`. It can be accessed in the controller, +    // For example: loading data from the database into the in-memory cache + this.app.cacheData = await app.model.query(QUERY_CACHE_SQL); + } -```js -// app/controller/home.js -class HomeController extends Controller { - async index() { - // 'app.cities' is loaded, and you can access it by 'this.ctx.app.cities' + async didReady() { + // Application already ready + + const ctx = await this.app.createAnonymousContext(); + await ctx.service.Biz.request(); + } + + async serverDidReady() { + // http / https server has started and begins accepting external requests +    // At this point you can get an instance of server from app.server + + app.server.on('timeout', socket => { + // handle socket timeout + }); } } ``` -**Note: When the framework executes the lifecycle method `beforeStart`, do not run time-consuming operation. The framework enables a *Timeout* setting by default when it starts up.** +**Note: It is not recommended to do too time-consuming operations in the custom lifecycle function, the framework will have a startup timeout detection.** diff --git a/docs/source/zh-cn/basics/app-start.md b/docs/source/zh-cn/basics/app-start.md index 0c135c0727..5596d484d7 100644 --- a/docs/source/zh-cn/basics/app-start.md +++ b/docs/source/zh-cn/basics/app-start.md @@ -3,34 +3,77 @@ title: 启动自定义 我们常常需要在应用启动期间进行一些初始化工作,等初始化完成后应用才可以启动成功,并开始对外提供服务。 -框架提供了统一的入口文件(`app.js`)进行启动过程自定义,这个文件只返回一个函数。例如,我们需要在应用启动期间从远程接口加载一份全国城市列表,以便于后续在 Controller 中使用: +框架提供了统一的入口文件(`app.js`)进行启动过程自定义,这个文件返回一个 Boot 类,我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。 + +框架提供了这些 [生命周期函数](../advanced/loader.md#life-cycles)供开发人员处理: + +- 配置文件即将加载,这是最后动态修改配置的时机(`configWillLoad`) +- 配置文件加载完成(`configDidLoad`) +- 文件加载完成(`didLoad`) +- 插件启动完毕(`willReady`) +- worker 准备就绪(`didReady`) +- 应用启动完成(`serverDidReady`) +- 应用即将关闭(`beforeClose`) + +我们可以在 `app.js` 中定义这个 Boot 类,下面我们抽取几个在应用开发中常用的生命周期函数来举例: ```js // app.js -module.exports = app => { - app.beforeStart(async () => { - // 应用会等待这个函数执行完成才启动 - app.cities = await app.curl('http://example.com/city.json', { - method: 'GET', - dataType: 'json', +class AppBootHook { + constructor(app) { + this.app = app; + } + + configWillLoad() { + // 此时 config 文件已经被读取并合并,但是还并未生效 + // 这是应用层修改配置的最后时机 + // 注意:此函数只支持同步调用 + + // 例如:参数中的密码是加密的,在此处进行解密 + this.app.config.mysql.password = decrypt(this.app.config.mysql.password); + // 例如:插入一个中间件到框架的 coreMiddleware 之间 + const statusIdx = app.config.coreMiddleware.indexOf('status'); + app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit'); + } + + async didLoad() { + // 所有的配置已经加载完毕 + // 可以用来加载应用自定义的文件,启动自定义的服务 + + // 例如:创建自定义应用的示例 + this.app.queue = new Queue(this.app.config.queue); + await this.app.queue.init(); + + // 例如:加载自定义的目录 + app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', { + fieldClass: 'tasksClasses', }); + } - // 也可以通过以下方式来调用 Service - // const ctx = app.createAnonymousContext(); - // app.cities = await ctx.service.cities.load(); - }); -}; -``` + async willReady() { + // 所有的插件都已启动完毕,但是应用整体还未 ready + // 可以做一些数据初始化等操作,这些操作成功才会启动应用 -在 Controller 中就可以使用了: + // 例如:从数据库加载数据到内存缓存 + this.app.cacheData = await app.model.query(QUERY_CACHE_SQL); + } -```js -// app/controller/home.js -class HomeController extends Controller { - async index() { - // 'app.cities' 在上面启动期间已经加载,可以直接通过 'this.ctx.app.cities' 使用。 + async didReady() { + // 应用已经启动完毕 + + const ctx = await this.app.createAnonymousContext(); + await ctx.service.Biz.request(); + } + + async serverDidReady() { + // http / https server 已启动,开始接受外部请求 + // 此时可以从 app.server 拿到 server 的实例 + + app.server.on('timeout', socket => { + // handle socket timeout + }); } } ``` -**注意:在 `beforeStart` 中不建议做太耗时的操作,框架会有启动的超时检测。** +**注意:在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。**