node.js——Alibaba enterprise-level service framework Egg construction

egg is an open source enterprise-level framework of Alibaba. The main design concept is to balance the technical differences between teams, focusing on providing the core functions of web development and a set of flexible and extensible plug-in mechanisms. Through Egg, the team's architects and technical leaders can easily expand a framework suitable for their own business scenarios on the basis of Egg based on their own technical architecture.

Egg's plug-in mechanism is highly scalable, and a plug-in only does one thing (for example, the Nunjucks template is encapsulated into egg-view-nunjucks, and the MySQL database is encapsulated into egg-mysql). Egg aggregates these plug-ins through the framework and customizes the configuration according to its own business scenarios, so that the development cost of the application becomes very low.

Egg adheres to "convention over configuration" and develops applications according to a set of unified conventions. Adopting this method within the team can reduce the learning cost of developers. Developers are no longer "nails" and can flow. Without an agreed team, the communication cost is very high. For example, some people will divide the stack according to the directory, while others will divide the functions according to the directory. If the developers have inconsistent cognition, it is easy to make mistakes. But agreement does not mean poor scalability. On the contrary, Egg has high scalability, and the framework can be customized according to the team's agreement. Using Loader allows the framework to define default configurations according to different environments, and can also override Egg's default conventions.

Official document   https://eggjs.org/zh-cn/intro/quickstart.html

Main modules:

1. Router

URL routing rules are defined in app/router.js

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/user/:id', controller.user.info);
};

Implement Controller under the app/controller directory

// app/controller/user.js
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    ctx.body = {
      name: `hello ${ctx.params.id}`,
    };
  }
}

In the definition of Router, it can support multiple Middleware to be executed in series
. The Controller must be defined in the app/controller directory.
A file can also contain multiple Controller definitions. When defining a route, you can specify the corresponding Controller through ${fileName}.${functionName}.
Controller supports subdirectories. When defining a route, you can formulate the corresponding Controller through ${directoryName}.${fileName}.${functionName}.

Routing definition method:

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/home', controller.home);
  router.get('/user/:id', controller.user.page);
  router.post('/admin', isAdmin, controller.admin);
  router.post('/user', isLoginUser, hasAdminPermission, controller.user.create);
  router.post('/api/v1/comments', controller.v1.comments.create); // app/controller/v1/comments.js
};

2. Controller

Controller is responsible for parsing user input and returning corresponding results after processing, for example

In the RESTful interface, the Controller accepts user parameters, finds content from the database and returns it to the user or updates the user's request to the database.
In the HTML page request, the Controller renders different templates according to different URLs accessed by the user, and returns HTML to the user.
In the proxy server, the Controller forwards the user's request to other servers, and returns the processing results of other servers to the user.
The framework recommends that the Controller layer mainly process (verify and convert) the user's request parameters, and then call the corresponding service method to process the business. After obtaining the business results, encapsulate and return:

Get the request parameters passed by the user through HTTP.
Calibration and assembly parameters.
Call the Service for business processing, and if necessary, process and transform the return result of the Service to adapt it to the user's needs.
Respond to the user with the result via HTTP.

Parameter acquisition:

Query String method

// app/router.js
module.exports = app => {
  app.router.get('/search', app.controller.search.index);
};

// app/controller/search.js
exports.index = async ctx => {
  ctx.body = `search: ${ctx.query.name}`;
};

Parameter naming method

// app/router.js
module.exports = app => {
  app.router.get('/user/:id/:name', app.controller.user.info);
};

// app/controller/user.js
exports.info = async ctx => {
  ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
};

return data:

ctx.response.body (abbreviated as ctx.body), it is not recommended to use shorthand syntax, because it will cause confusion and misunderstanding with ctx.request.body . As an enterprise-level application development, it is necessary to consider the readability and learning efficiency for newcomers to take over.

 

Redirect:

The framework overrides koa's native ctx.redirect implementation through the security plugin to provide more secure redirection.

ctx.redirect(url) If it is not in the configured whitelist domain name, redirection is prohibited.
ctx.unsafeRedirect(url) does not judge the domain name, and directly redirects. It is generally not recommended to use it. Use it after clearly understanding the possible risks.
If the user uses the ctx.redirect method, the following configuration needs to be done in the application configuration file:

// config/config.default.js
exports.security = {
  domainWhiteList:['.domain.com'],  // 安全白名单,以 . 开头
};


If the user does not configure domainWhiteList or the domainWhiteList array is empty, all redirect requests will be released by default, which is equivalent to ctx.unsafeRedirect(url)

3. Service

Service is an abstraction layer for business logic encapsulation in complex business scenarios. Providing this abstraction has the following advantages:

Keep the logic in the Controller more concise.
To maintain the independence of business logic, the abstracted Service can be called repeatedly by multiple Controllers.
Separating logic and presentation makes it easier to write test cases. For details on how to write test cases, see here.

Define Service

// 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;

Detailed Service ctx

In order to obtain the link requested by the user, we inject the request context in the Service initialization, and the user can directly obtain context-related information through this.ctx in the method. For a detailed explanation of the context, please refer to Context. With ctx, we can get various convenient properties and methods encapsulated by the framework. For example we can use:

this.ctx.curl makes a network call.
this.ctx.service.otherService calls other Service.
this.ctx.db initiates database calls, etc. db may be a module that other plug-ins mount to the app in advance.
 

// app/router.js
module.exports = app => {
  app.router.get('/user/:id', app.controller.user.info);
};

// 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;

common problem:

Start the service:

npm run dev

PM2 starts:

egg is deprecated to start with PM2, but it can still be used.

Define the startup file in the project root directory:

// server.js
const egg = require('egg');

const workers = Number(process.argv[2] || require('os').cpus().length);
egg.startCluster({
  workers,
  baseDir: __dirname,
});
pm2 start server.js

Modify port:

/config/config.default.js

  config.cluster = {
    listen: {
      path: '',
      port: 7002,
      hostname: '0.0.0.0',
    },
  };

csrf error:

Egg's built-in egg-security plugin performs CSRF verification on all "non-secure" methods such as POST, PUT, and DELETE by default.

When the request encounters csrf error, it is usually caused by not adding the correct csrf token. If no csrf protection is required, set it in /config/config.default.js:

config.security = {
    csrf: {
      enable: false,
    },
  };

For csrf verification, read  the security threat CSRF prevention

Development example:

//app/router.js
'use strict';
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/Title1', controller.test.Title1);
  router.post('/Title2', controller.test.Title2);
};
//app/controller/test.js
'use strict';
const Controller = require('egg').Controller;
class TestController extends Controller {
  async Title1() {
    const query = this.ctx.query;
    const adds = query.l1 + '  ' + query.l2;
    const dataList = {
      list: [
        { id: 1, title: 'this is news 1', url: '/news/1', l1: query.l1, sum: adds },
        { id: 2, title: 'this is news 2', url: '/news/2', l2: query.l2, sum: adds }],
    };
    this.ctx.response.body = dataList;
  }
  async Title2() {

    const adds = this.ctx.request.body.l1 + this.ctx.request.body.l2;
    const dataList = {
      list: [
        { id: 1, title: 'this is news 1', url: '/news/1', sum: adds },
        { id: 2, title: 'this is news 2', url: '/news/2', sum: adds }],
    };
    this.ctx.response.body = dataList;
  }
}
module.exports = TestController;

 

Guess you like

Origin blog.csdn.net/sm9sun/article/details/103290463