Разберитесь с Eggjs в одной статье — исчерпывающее объяснение основ (о)

Egg.js
Разберитесь с Eggjs в одной статье — исчерпывающее объяснение основ (о)

Egg наследует Koa, Egg выбирает Koa в качестве базовой структуры и дополнительно совершенствует ее на основе своей модели.

Выполнение анализа процесса

Промежуточное ПО Koa выбирает модель лукового кольца.

中间件洋葱图

Все запросы будут выполняться дважды, когда они проходят через промежуточное программное обеспечение.По сравнению с промежуточным программным обеспечением Express, модель Koa может легко реализовать логику постобработки.Вы можете видеть, что выполнение выполняется спереди назад, а затем сзади вперед.

Диаграмма последовательности выполнения промежуточного программного обеспечения

中间件执行顺序图

Анализ структуры каталогов

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

Каталоги, согласованные в рамках:

  • app/router.jsИспользуется для настройки правил маршрутизации URL
  • app/controller/**Используется для анализа ввода пользователя и возврата соответствующего результата после обработки.
  • app/service/**Используется для написания уровня бизнес-логики, необязательно
  • app/middleware/**для написания промежуточного ПО, опционально
  • app/public/**Используется для размещения статических ресурсов, опционально
  • app/extend/**Расширения для фреймворка, опционально
  • config/config.{env}.jsДля записи файлов конфигурации
  • config/plugin.jsИспользуется для настройки подключаемых модулей для загрузки.
  • test/**для модульного тестирования
  • app.jsа такжеagent.jsИспользуется для настройки работы инициализации при запуске, опционально

Каталоги, согласованные встроенными плагинами:

  • app/public/**Используется для размещения статических ресурсов, опционально
  • app/schedule/**для запланированных задач

порядок загрузки файлов

Egg относится к приложениям, фреймворкам и плагинам как loadUnits.

документ заявление Рамка плагин
package.json ✔︎ ︎ ✔
config/plugin.{env}.js ✔︎ ✔︎
config/config.{env}.js ✔︎ ✔︎ ✔︎
app/extend/application.js ✔︎ ︎ ✔︎
app/extend/request.js ✔︎ ✔︎ ✔︎
app/extend/response.js ✔︎ ✔︎ ✔︎
app/extend/context.js ✔︎ ✔︎ ✔︎
app/extend/helper.js ✔︎ ✔︎ ✔︎
agent.js ✔︎ ✔︎ ✔︎
app.js ✔︎ ✔︎ ✔︎
app/service ✔︎ ✔︎ ✔︎
app/middleware ✔︎ ✔︎ ✔︎
app/controller ✔︎
app/router.js ✔︎

Файлы загружаются сверху вниз в порядке в таблице, Egg будет проходить через все loadUnits, чтобы загрузить вышеуказанные файлы (приложения, фреймворки и плагины разные), и при загрузке есть определенный приоритет.

  • Загружается последовательно плагином => фреймворк => приложение
  • Порядок между плагинами определяется отношениями зависимости, зависимая сторона загружается первой, и никакие зависимости не загружаются в соответствии с порядком конфигурации ключа объекта.
  • Фреймворки загружаются в порядке наследования, чем ниже уровень, тем первым

Жизненный цикл

  • Файл конфигурации вот-вот будет загружен, это последний раз, когда нужно динамически изменить конфигурацию (configWillLoad)
  • Файл конфигурации загружается (configDidLoad)
  • загрузка файла завершена (didLoad)
  • Плагин запущен(willReady)
  • Рабочий готов(didRead)
  • Запуск приложения завершен (serverDidReady)
  • Приложение вот-вот закроется (beforeClose)

Определяется следующим образом:

// app.js or agent.js
class AppBootHook {
  constructor(app) {
    this.app = app;
  }
  configWillLoad() {
    // Ready to call configDidLoad,
    // Config, plugin files are referred,
    // this is the last chance to modify the config.
    console.log('configWillLoad');
  }
  configDidLoad() {
    // Config, plugin files have been loaded.
    console.log('configDidLoad');
  }
  async didLoad() {
    // All files have loaded, start plugin here.
    console.log('didLoad');
  }
  async willReady() {
    // All plugins have started, can do some thing before app ready
    console.log('willReady');
  }
  async didReady() {
    // Worker is ready, can do some things
    // don't need to block the app boot.
    console.log('didReady');
  }
  async serverDidReady() {
    // Server is listening.
    console.log('serverDidReady');
  }
  async beforeClose() {
    // Do some thing before app close.
    console.log('configWillLoad');
  }
}
module.exports = AppBootHook;

Последовательность выполнения при запуске

Встроенные базовые объекты фреймворка*

doc

Некоторые базовые объекты встроены в фреймворк, в том числе 4, унаследованные от Koa.

  • Application
  • Context
  • Request
  • Response

и некоторые объекты, которые расширяет фреймворк

  • Controller
  • Service
  • Helper
  • Config
  • Logger

Application

Приложение — это глобальный объект приложения. В приложении будет создан только один экземпляр. Он наследуется отKoa.Application, на который мы можем смонтировать некоторые глобальные методы и объекты.

мероприятие

  • serverЭто событие будет инициировано рабочим процессом только один раз.После запуска службы HTTP сервер HTTP будет доступен разработчику через это событие.
  • errorПосле того, как плагин onerror перехватит любое исключение во время выполнения, будет запущено событие ошибки.
  • requestа такжеresponseКогда приложение получает запрос и отвечает на запрос, оно инициирует события запроса и ответа соответственно.
// app.js
module.exports = app => {
  app.once('server', server => {
    // websocket
    console.log('server', server);
    
  });
  app.on('error', (err, ctx) => {
    // report error
    console.log('error', err);
  });
  app.on('request', ctx => {
    // log receive request
    console.log('request');
  });
  app.on('response', ctx => {
    // ctx.starttime is set by framework
    const used = Date.now() - ctx.starttime;
    // log total cost
    console.log('used', used);
  });
};

// 初始化的时候会打印 server Server 
// request
// used 6
// request
// used 1

способ получения

Почти все файлы, загружаемые загрузчиком фреймворка (контроллер, служба, расписание и т. д.), могут экспортировать функцию, которая будет вызываться загрузчиком с приложением в качестве параметра.

  • Запустить пользовательский скрипт
// app.js
module.exports = app => {
  app.cache = new Cache();
};
  • Файл контроллера
// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    this.ctx.body = this.app.cache.get(this.ctx.query.id);
  }
}

Как и Koa, в объекте Context доступ к объекту Application можно получить через ctx.app. то есть

результат верен

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id);
  }
}

Context

Контекст — это объект уровня запроса, унаследованный отKoa.Context

Каждый раз, когда поступает пользовательский запрос, платформа создает экземпляр объекта Context, который инкапсулирует информацию, запрошенную пользователем, и предоставляет множество удобных методов для получения параметров запроса или установки информации ответа.

способ получения


// Koa v1
function* middleware(next) {
  // this is instance of Context
  console.log(this.query);
  yield next;
}

// Koa v2
async function middleware(ctx, next) {
  // ctx is instance of Context
  console.log(ctx.query);
}

В дополнение к получению экземпляра Context во время запроса, в некоторых сценариях, которые не запрошены пользователем, нам необходимо получить доступ к объектам экземпляра Context, таким как служба/модель, которые можно использоватьApplication.createAnonymousContext()метод создает анонимный экземпляр Context.

// app.js
module.exports = app => {
  app.beforeStart(async () => {
    const ctx = app.createAnonymousContext();
    // preload before app start
    await ctx.service.posts.load();
  });
}

Каждая задача в запланированной задаче принимает экземпляр Context в качестве параметра.

// app/schedule/refresh.js
exports.task = async ctx => {
  await ctx.service.posts.refresh();
};

Request & Response

  • Requestобъект уровня запроса, унаследованный отKoa.Request. Он инкапсулирует собственный объект HTTP-запроса Node.js и предоставляет ряд вспомогательных методов для получения общих параметров HTTP-запросов.
  • Responseобъект уровня запроса, унаследованный отKoa.Response. Он инкапсулирует собственный объект HTTP-ответа Node.js и предоставляет ряд вспомогательных методов для установки HTTP-ответа.

способ получения

Вы можете получить запрос текущего запроса (ctx.request) и Ответ(ctx.response) пример.

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    const { app, ctx } = this;
    const id = ctx.request.query.id;
    ctx.response.body = app.cache.get(id);
  }
}
  • Koa будет проксировать некоторые методы и свойства в запросе и ответе в контексте, см.Koa.Context
  • как в примере вышеctx.request.query.idа такжеctx.query.idэквивалентно,ctx.response.body=а такжеctx.body=эквивалентен
  • Следует отметить, что для получения тела POST следует использоватьctx.request.body, вместоctx.body

Controller

Платформа предоставляет базовый класс контроллера и рекомендует, чтобы все контроллеры наследуются от реализации этого базового класса.

Базовый класс Controller имеет следующие свойства:

  • ctx- экземпляр контекста текущего запроса
  • app- Экземпляр приложения
  • config- Конфигурация приложения
  • service- применять все услуги
  • logger- объект логгера, инкапсулированный для текущего контроллера

В файле Controller на базовый класс Controller можно ссылаться двумя способами:

// app/controller/user.js

// 从 egg 上获取(推荐)
const Controller = require('egg').Controller;
class UserController extends Controller {
  // implement
}
module.exports = UserController;

// 从 app 实例上获取
module.exports = app => {
  return class UserController extends app.Controller {
    // implement
  };
};

Service

Платформа предоставляет базовый класс службы и рекомендует, чтобы все службы наследуются от реализации этого базового класса.

Свойства базового класса Service такие же, как у базового класса Controller, и методы доступа аналогичны:

// app/service/user.js

// 从 egg 上获取(推荐)
const Service = require('egg').Service;
class UserService extends Service {
  // implement
}
module.exports = UserService;

// 从 app 实例上获取
module.exports = app => {
  return class UserService extends app.Service {
    // implement
  };
};

Helper

Helper используется для предоставления некоторых полезных служебных функций. Его функция заключается в том, что мы можем выделить некоторые часто используемые действия в независимую функцию в helper.js, чтобы мы могли использовать JavaScript для написания сложной логики, избежать разбросанной повсюду логики и лучше писать тестовые примеры.

Сам помощник класса, и имейте одинаковые атрибуты контроллера базового класса, он будет создан для каждого запроса, все функции на хелпере могут получить текущую информацию контекста, связанную с запросом.

способ получения

Вы можете получить текущий запрошенный помощник в экземпляре Context (ctx.helper) пример.

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    const { app, ctx } = this;
    const id = ctx.query.id;
    const user = app.cache.get(id);
    ctx.body = ctx.helper.formatUser(user);
  }
}

собственный вспомогательный метод

// app/extend/helper.js
module.exports = {
  formatUser(user) {
    return only(user, [ 'name', 'phone' ]);
  }
};

Config

Рекомендуется, чтобы разработка приложений следовала принципу разделения конфигурации и кода и помещала некоторые бизнес-конфигурации, которые необходимо жестко закодировать в файл конфигурации.В то же время файл конфигурации поддерживает различные операционные среды для использования разных конфигураций, которые также очень удобен в использовании.Все фреймворки и плагины И конфигурация уровня приложения могут быть получены через объект Config.

способ получения

пройти черезapp.configПолучите объект конфигурации из экземпляра приложения или передайте его экземплярам контроллера, службы, помощника.this.configПолучите объект конфигурации.

Logger

Каждый объект регистратора предоставляет четыре уровня методов:

  • logger.debug()
  • logger.info()
  • logger.warn()
  • logger.error()

способ получения

  • app.loggerЕсли мы хотим вести журнал на уровне приложения, например, записывать некоторую информацию о данных на этапе запуска и записывать некоторую независимую от бизнеса информацию, мы можем сделать это с помощью App Logger.
  • app.coreLoggerПри разработке приложений не следует использовать CoreLogger для печати журналов, но фреймворки и плагины должны использовать его для печати журналов на уровне приложения, чтобы можно было более четко различать журналы, печатаемые приложением и фреймворком. by CoreLogger будет помещен в а Logger в разные файлы
  • ctx.loggerОн получен из экземпляра Context.Из метода доступа мы видим, что Context Logger должен быть связан с запросом, а журналы, которые он печатает, принесут некоторую информацию, связанную с текущим запросом впереди (например,[$userId/$ip/$traceId/${cost}ms $method $url])
  • this.loggerМожет передавать экземпляры контроллера и службыthis.loggerПолучите их, по сути, это Context Logger, но при печати журнала будет добавлен дополнительный путь к файлу.