введение яиц
что такое яйцо?
Egg — это серверная веб-инфраструктура node.js, созданная Alibaba, основанная на пакете koa и имеющая некоторые соглашения.
Почему его называют яйцом?
яйцо имеетвоспитывать, потому что расположение яйцаВеб-инфраструктура предприятия, призванный помочь разработчикам создавать фреймворки, которые работают для их команд.
Какие продукты разработаны с использованием яиц?
язык птицаОн разработан с помощью egg.Схема архитектуры выглядит следующим образом:
Какие компании используют яйца?
Hema, Zhuanzhuan подержанные автомобили, PingWest, Xiaomi, 58.com и т. д. (ссылка для выбора стека технологийСвязь)
Поддерживает ли яйцо Typescript?
Хотя само яйцо написано на JavaScript, приложение яйца можно написать на Typescript, просто используйте следующую команду для создания проекта (см.Связь):
$ npx egg-init --type=ts showcase
Будет ли intellisense при написании яиц на JavaScript?
Да, если в package.json будет добавлено следующее объявление, каталог typings будет динамически генерироваться в корневом каталоге проекта, который содержит объявления типов для различных моделей (см.Связь):
"egg": {
"declarations": true
}
Какая связь между яйцом и коа?
коа является базовой структурой яйца, а яйцо является усовершенствованием коа.
Нужен ли коа, чтобы выучить яйца?
Если вы не знаете коа, вы можете сразу начать работу с яйцами, но знание коа поможет вам понять яйца на более глубоком уровне.
Создать проект
Мы используем базовый шаблон и выбираем домашнее зеркало для создания проекта яйца:
$ npm init egg --type=simple --registry=china
# 或者 yarn create egg --type=simple --registry=china
объяснятьnpm init egg
Этот синтаксис:
Представлена версия npm@6
npm-init <initializer>
синтаксис, эквивалентныйnpx create-<initializer>
команда, покаnpx
команда пойдет$PATH
путь иnode_modules/.bin
Найдите путь с именемcreate-<initializer>
Исполняемый файл, если он найден, то будет выполнен, а если не найден, то будет установлен.То есть,
npm init egg
пойду найду или скачаюcreate-egg
исполняемый, покаcreate-eggпакетegg-initПсевдоним пакета, который эквивалентен вызовуegg-init
.
После создания структура каталогов выглядит следующим образом (игнорируйте файл README и тестовый каталог):
├── app
│ ├── controller
│ │ └── home.js
│ └── router.js
├── config
│ ├── config.default.js
│ └── plugin.js
├── package.json
Это минимальный проект яйца, использующийnpm i
илиyarn
После установки зависимостей выполните команду запуска:
$ npm run dev
[master] node version v14.15.1
[master] egg version 2.29.1
[master] agent_worker#1:23135 started (842ms)
[master] egg started on http://127.0.0.1:7001 (1690ms)
Открытьhttp://127.0.0.1:7001/
будет отображаться на веб-страницеhi, egg
.
соглашение о каталогах
Проект, созданный выше, представляет собой лишь минимальную структуру, типичный проект яйца имеет следующую структуру каталогов:
egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app/
| ├── router.js # 用于配置 URL 路由规则
│ ├── controller/ # 用于存放控制器(解析用户的输入、加工处理、返回结果)
│ ├── model/ (可选) # 用于存放数据库模型
│ ├── service/ (可选) # 用于编写业务逻辑层
│ ├── middleware/ (可选) # 用于编写中间件
│ ├── schedule/ (可选) # 用于设置定时任务
│ ├── public/ (可选) # 用于放置静态资源
│ ├── view/ (可选) # 用于放置模板文件
│ └── extend/ (可选) # 用于框架的扩展
│ ├── helper.js (可选)
│ ├── request.js (可选)
│ ├── response.js (可选)
│ ├── context.js (可选)
│ ├── application.js (可选)
│ └── agent.js (可选)
├── config/
| ├── plugin.js # 用于配置需要加载的插件
| ├── config.{env}.js # 用于编写配置文件(env 可以是 default,prod,test,local,unittest)
Это согласовано с каркасом яйца или встроенными плагинами и является передовой практикой, подытоженной Али.Хотя каркас также предоставляет пользователям возможность настраивать структуру каталогов, по-прежнему рекомендуется использовать это решение из Али. В следующих главах функции вышеупомянутых условных каталогов и файлов будут объяснены один за другим.
Маршрутизатор
маршрут определенПуть запроса (URL)а такжеКонтроллерОтношение сопоставления между ними, то есть какой контроллер должен обрабатывать URL-адрес, к которому обращается пользователь. мы открытыapp/router.js
посмотри:
module.exports = app => {
const { router, controller } = app
router.get('/', controller.home.index)
};
Как видите, файл маршрутизации экспортирует функцию, которая получает объект приложения в качестве параметра и определяет отношение сопоставления с помощью следующего синтаксиса:
router.verb('path-match', controllerAction)
вverb
Обычно в нижнем регистре HTTP-глаголы, например:
- HEAD -
router.head
- OPTIONS -
router.options
- GET -
router.get
- PUT -
router.put
- POST -
router.post
- PATCH -
router.patch
- DELETE -
router.delete
илиrouter.del
Кроме этого, есть специальный глаголrouter.redirect
Указывает на перенаправление.
а такжеcontrollerAction
определяется синтаксисом точки (·)controller
Конкретная функция в файле в каталоге, например:
controller.home.index // 映射到 controller/home.js 文件的 index 方法
controller.v1.user.create // controller/v1/user.js 文件的 create 方法
Вот несколько примеров и их пояснения:
module.exports = app => {
const { router, controller } = app
// 当用户访问 news 会交由 controller/news.js 的 index 方法进行处理
router.get('/news', controller.news.index)
// 通过冒号 `:x` 来捕获 URL 中的命名参数 x,放入 ctx.params.x
router.get('/user/:id/:name', controller.user.info)
// 通过自定义正则来捕获 URL 中的分组参数,放入 ctx.params 中
router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, controller.package.detail)
}
Помимо использования глаголов для создания маршрутов, egg также предоставляет следующий синтаксис для быстрого создания маршрутов CRUD:
// 对 posts 按照 RESTful 风格映射到控制器 controller/posts.js 中
router.resources('posts', '/posts', controller.posts)
Автоматически генерируются следующие маршруты:
HTTP-метод | путь запроса | название маршрута | функция контроллера |
---|---|---|---|
GET | /posts | posts | app.controller.posts.index |
GET | /posts/new | new_post | app.controller.posts.new |
GET | /posts/:id | post | app.controller.posts.show |
GET | /posts/:id/edit | edit_post | app.controller.posts.edit |
POST | /posts | posts | app.controller.posts.create |
PATCH | /posts/:id | post | app.controller.posts.update |
DELETE | /posts/:id | post | app.controller.posts.destroy |
Вам нужно только реализовать соответствующий метод в контроллере. |
Когда проект будет становиться все больше и больше, будет все больше и больше сопоставлений маршрутизации. Мы можем захотеть иметь возможность разделить сопоставления маршрутизации по файлам. В настоящее время есть два способа:
-
Ручной импорт, то есть запись файла маршрутизации в
app/router
каталог, а затемapp/router.js
импортировать эти файлы. Образец кода:// app/router.js module.exports = app => { require('./router/news')(app) require('./router/admin')(app) }; // app/router/news.js module.exports = app => { app.router.get('/news/list', app.controller.news.list) app.router.get('/news/detail', app.controller.news.detail) }; // app/router/admin.js module.exports = app => { app.router.get('/admin/user', app.controller.admin.user) app.router.get('/admin/log', app.controller.admin.log) };
-
использоватьegg-router-plusПлагины автоматически импортируются
app/router/**/*.js
и предоставляет функцию пространства имен:// app/router.js module.exports = app => { const subRouter = app.router.namespace('/sub') subRouter.get('/test', app.controller.sub.test) // 最终路径为 /sub/test }
В дополнение к HTTP-глаголу маршрутизатор также предоставляет метод перенаправления для внутреннего перенаправления, например:
module.exports = app => {
app.router.get('index', '/home/index', app.controller.home.index)
app.router.redirect('/', '/home/index', 302)
}
ПО промежуточного слоя
egg предусматривает, что промежуточное программное обеспечение помещается вapp/middleware
Отдельный файл в каталоге, который нужно экспортировать обычной функцией, принимающей два аргумента:
- варианты: элементы конфигурации промежуточного программного обеспечения, фреймворк будет
app.config[${middlewareName}]
пройти в. - app: экземпляр текущего приложения Application.
мы создаем новыйmiddleware/slow.js
Промежуточное ПО медленных запросов, когда время запроса превышает указанный нами порог, печатает журнал, код такой:
module.exports = (options, app) => {
return async function (ctx, next) {
const startTime = Date.now()
await next()
const consume = Date.now() - startTime
const { threshold = 0 } = options || {}
if (consume > threshold) {
console.log(`${ctx.url}请求耗时${consume}毫秒`)
}
}
}
затем вconfig.default.js
используется в:
module.exports = {
// 配置需要的中间件,数组顺序即为中间件的加载顺序
middleware: [ 'slow' ],
// slow 中间件的 options 参数
slow: {
enable: true
},
}
Сконфигурированное здесь промежуточное ПО включено глобально, если вы просто хотите использовать промежуточное ПО на указанном маршруте, например, только для/api
Если запрос URL, начинающийся с префикса, использует определенное промежуточное ПО, есть два способа:
-
существует
config.default.js
Установите атрибут совпадения или игнорирования в конфигурации:module.exports = { middleware: [ 'slow' ], slow: { threshold: 1, match: '/api' }, };
-
в файле маршрутизации
router.js
введен вmodule.exports = app => { const { router, controller } = app // 在 controller 处理之前添加任意中间件 router.get('/api/home', app.middleware.slow({ threshold: 1 }), controller.home.index) }
egg делит промежуточное ПО на промежуточное ПО, определяемое прикладным уровнем (app.config.appMiddleware
) и промежуточное ПО по умолчанию (app.config.coreMiddleware
), давайте распечатаем это:
module.exports = app => {
const { router, controller } = app
console.log(app.config.appMiddleware)
console.log(app.config.coreMiddleware)
router.get('/api/home', app.middleware.slow({ threshold: 1 }), controller.home.index)
}
оказаться:
// appMiddleware
[ 'slow' ]
// coreMiddleware
[
'meta',
'siteFile',
'notfound',
'static',
'bodyParser',
'overrideMethod',
'session',
'securities',
'i18n',
'eggLoaderTrace'
]
Среди них coreMiddleware — промежуточное программное обеспечение, созданное для нас egg.Оно включено по умолчанию.Если вы не хотите его использовать, вы можете отключить его, настроив:
module.exports = {
i18n: {
enable: false
}
}
Контроллер
Контролер несет ответственностьПроанализируйте ввод пользователя и верните соответствующий результат после обработки., простейший пример helloworld:
const { Controller } = require('egg');
class HomeController extends Controller {
async index() {
const { ctx } = this;
ctx.body = 'hi, egg';
}
}
module.exports = HomeController;
Конечно, код в нашем реальном проекте будет не таким простым, обычно в контроллере делаются следующие вещи:
- Получение, проверка и обработка параметров HTTP-запроса
- Вызовите службу (Service) для обработки дела
- Отвечать пользователю по HTTP
Реальный случай таков:
const { Controller } = require('egg');
class PostController extends Controller {
async create() {
const { ctx, service } = this;
const createRule = {
title: { type: 'string' },
content: { type: 'string' },
};
// 校验和组装参数
ctx.validate(createRule);
const data = Object.assign(ctx.request.body, { author: ctx.session.userId });
// 调用 Service 进行业务处理
const res = await service.post.create(data);
// 响应客户端数据
ctx.body = { id: res.id };
ctx.status = 201;
}
}
module.exports = PostController;
Поскольку Controller — это класс, общие методы могут быть инкапсулированы путем настройки базового класса, например:
// app/core/base_controller.js
const { Controller } = require('egg');
class BaseController extends Controller {
get user() {
return this.ctx.session.user;
}
success(data) {
this.ctx.body = { success: true, data };
}
notFound(msg) {
this.ctx.throw(404, msg || 'not found');
}
}
module.exports = BaseController;
Затем пусть все контроллеры наследуют этот пользовательский BaseController:
// app/controller/post.js
const Controller = require('../core/base_controller');
class PostController extends Controller {
async list() {
const posts = await this.service.listByUser(this.user);
this.success(posts);
}
}
Пройти в контроллерthis.ctx
Вы можете получить объект контекста, который удобен для получения и установки связанных параметров, например:
-
ctx.query
: параметры запроса в URL (игнорировать повторяющиеся ключи) -
ctx.quries
: параметры запроса в URL (повторяющиеся ключи помещаются в массив) -
ctx.params
: Именованные параметры на маршрутизаторе -
ctx.request.body
: содержимое тела HTTP-запроса. -
ctx.request.files
: Файловый объект, загруженный внешним интерфейсом -
ctx.getFileStream()
: получить поток загруженных файлов -
ctx.multipart()
:Получатьmultipart/form-data
данные -
ctx.cookies
: читать и устанавливать куки -
ctx.session
: прочитать и установить сеанс -
ctx.service.xxx
: получить экземпляр указанного объекта службы (отложенная загрузка). -
ctx.status
: установить код состояния -
ctx.body
: установить тело ответа -
ctx.set
: установить заголовок ответа -
ctx.redirect(url)
: перенаправление -
ctx.render(template)
: шаблон рендеринга
this.ctx
Объект контекста является наиболее важным объектом в каркасе яйца и каркасе коа.Нам нужно выяснить функцию этого объекта, но следует отметить, что некоторые свойства не связаны напрямую сapp.ctx
На объекте, но проксируя свойства объекта запроса или ответа, мы можем использоватьObject.keys(ctx)
посмотри:
[
'request', 'response', 'app', 'req', 'res', 'onerror', 'originalUrl', 'starttime', 'matched',
'_matchedRoute', '_matchedRouteName', 'captures', 'params', 'routerName', 'routerPath'
]
Оказание услуг
Служба — это реализация определенной бизнес-логики.Инкапсулированная служба может вызываться несколькими контроллерами, а контроллер также может вызывать несколько служб.Хотя бизнес-логика также может быть написана в контроллере, делать это не рекомендуется.Логика контроллера должны быть простыми и играть только роль «моста».
Контроллер может вызывать любой метод на любом сервисе.Стоит отметить, что сервис загружается отложенно, то есть фреймворк будет создавать его экземпляр только при доступе к нему.
Обычно в Сервисе выполняются следующие действия:
- Работа со сложной бизнес-логикой
- Базы данных вызовов или сторонние сервисы (например, сбор информации GitHub и т. д.)
Простой пример службы, которая возвращает результаты запроса в базу данных:
// 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;
Его можно вызвать прямо в контроллере:
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;
}
}
Обратите внимание, что файл службы должен быть помещен в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
Задержка создания экземпляра, чтобы служба могла получить контекст текущего запроса через this.ctx.
Рендеринг шаблона
Каркас яйца имеет встроенныйegg-viewВ качестве шаблонного решения он поддерживает различные шаблоны рендеринга, такие как ejs, handlebars, nunjunks и другие механизмы шаблонов.Каждый механизм шаблонов представлен как плагин.По умолчанию все плагины найдутapp/view
файлы в каталоге, а затем в соответствии сconfig\config.default.js
сопоставление суффиксов, определенное в для выбора другого механизма шаблонов:
config.view = {
defaultExtension: '.nj',
defaultViewEngine: 'nunjucks',
mapping: {
'.nj': 'nunjucks',
'.hbs': 'handlebars',
'.ejs': 'ejs',
},
}
Приведенная выше конфигурация указывает, что когда файл:
- суффикс
.nj
При использовании механизма шаблонов nunjunks - суффикс
.hbs
При использовании шаблонизатора руля - суффикс
.ejs
При использовании механизма шаблонов ejs - По умолчанию, когда суффикс не указан
.html
- По умолчанию используется nunjunks, если не указан механизм шаблонов.
Далее мы устанавливаем плагин шаблонизатора:
$ npm i egg-view-nunjucks egg-view-ejs egg-view-handlebars --save
# 或者 yarn add egg-view-nunjucks egg-view-ejs egg-view-handlebars
затем вconfig/plugin.js
Включите плагин в:
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks',
}
exports.handlebars = {
enable: true,
package: 'egg-view-handlebars',
}
exports.ejs = {
enable: true,
package: 'egg-view-ejs',
}
затем добавьтеapp/view
каталог, добавьте в него несколько файлов:
app/view
├── ejs.ejs
├── handlebars.hbs
└── nunjunks.nj
Коды:
<!-- ejs.ejs 文件代码 -->
<h1>ejs</h1>
<ul>
<% items.forEach(function(item){ %>
<li><%= item.title %></li>
<% }); %>
</ul>
<!-- handlebars.hbs 文件代码 -->
<h1>handlebars</h1>
{{#each items}}
<li>{{title}}</li>
{{~/each}}
<!-- nunjunks.nj 文件代码 -->
<h1>nunjunks</h1>
<ul>
{% for item in items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
Затем настройте маршрутизацию в Router:
module.exports = app => {
const { router, controller } = app
router.get('/ejs', controller.home.ejs)
router.get('/handlebars', controller.home.handlebars)
router.get('/nunjunks', controller.home.nunjunks)
}
Далее реализуем логику контроллера:
const Controller = require('egg').Controller
class HomeController extends Controller {
async ejs() {
const { ctx } = this
const items = await ctx.service.view.getItems()
await ctx.render('ejs.ejs', {items})
}
async handlebars() {
const { ctx } = this
const items = await ctx.service.view.getItems()
await ctx.render('handlebars.hbs', {items})
}
async nunjunks() {
const { ctx } = this
const items = await ctx.service.view.getItems()
await ctx.render('nunjunks.nj', {items})
}
}
module.exports = HomeController
Заносим данные в Сервис:
const { Service } = require('egg')
class ViewService extends Service {
getItems() {
return [
{ title: 'foo', id: 1 },
{ title: 'bar', id: 2 },
]
}
}
module.exports = ViewService
Посетите следующий адрес, чтобы увидеть результаты, обработанные различными механизмами шаблонов:
GET http://localhost:7001/nunjunks
GET http://localhost:7001/handlebars
GET http://localhost:7001/ejs
Вы спросите, а откуда взялся метод ctx.render? Правильно, это обеспечивается расширением контекста egg-view, которое добавляет к объекту контекста ctxrender
,renderView
а такжеrenderString
Три метода, код выглядит следующим образом:
const ContextView = require('../../lib/context_view')
const VIEW = Symbol('Context#view')
module.exports = {
render(...args) {
return this.renderView(...args).then(body => {
this.body = body;
})
},
renderView(...args) {
return this.view.render(...args);
},
renderString(...args) {
return this.view.renderString(...args);
},
get view() {
if (this[VIEW]) return this[VIEW]
return this[VIEW] = new ContextView(this)
}
}
В конечном итоге он перенаправит вызов метода рендеринга в экземпляре ContextView.ContextView — это класс, который может помочь нам найти соответствующий механизм рендеринга в соответствии с сопоставлением, определенным в конфигурации.
плагин
Когда мы объясняли рендеринг шаблонов на прошлом уроке, мы уже знали, как использовать плагины, то есть нужно только быть в приложении или фреймворке.config/plugin.js
Заявление в:
exports.myPlugin = {
enable: true, // 是否开启
package: 'egg-myPlugin', // 从 node_modules 中引入
path: path.join(__dirname, '../lib/plugin/egg-mysql'), // 从本地目录中引入
env: ['local', 'unittest', 'prod'], // 只有在指定运行环境才能开启
}
После включения плагина вы можете использовать функции, предоставляемые плагином:
app.myPlugin.xxx()
Если плагин содержит конфигурацию, которую должен настроить пользователь, ее можноconfig.default.js
Укажите, например:
exports.myPlugin = {
hello: 'world'
}
Плагин на самом деле представляет собой «мини-приложение», которое содержитService,промежуточное ПО,настроить,удлинение рамыд., но не независимыйRouterа такжеController, и вы не можете определить свой собственныйplugin.js
.
Очень важно подключаться к базе данных в процессе разработки, и наиболее практичным подключаемым модулем является подключаемый модуль для интеграции с базой данных.
Интегрировать MongoDB
Сначала убедитесь, что база данных MongoDB установлена и запущена на компьютере.Если это компьютер Mac, вы можете быстро установить и запустить его с помощью следующих команд:
$ brew install mongodb-community
$ brew services start mongodb/brew/mongodb-community # 后台启动
# 或者使用 mongod --config /usr/local/etc/mongod.conf 前台启动
затем установитеegg-mongooseПлагин:
$ npm i egg-mongoose
# 或者 yarn add egg-mongoose
существуетconfig/plugin.js
Откройте плагин в:
exports.mongoose = {
enable: true,
package: 'egg-mongoose',
}
существуетconfig/config.default.js
Определите параметры подключения в:
config.mongoose = {
client: {
url: 'mongodb://127.0.0.1/example',
options: {}
}
}
затем вmodel/user.js
Определите модель в:
module.exports = app => {
const mongoose = app.mongoose
const UserSchema = new mongoose.Schema(
{
username: {type: String, required: true, unique: true}, // 用户名
password: {type: String, required: true}, // 密码
},
{ timestamps: true } // 自动生成 createdAt 和 updatedAt 时间戳
)
return mongoose.model('user', UserSchema)
}
Вызовите метод мангуста в контроллере:
const {Controller} = require('egg')
class UserController extends Controller {
// 用户列表 GET /users
async index() {
const {ctx} = this
ctx.body = await ctx.model.User.find({})
}
// 用户详情 GET /users/:id
async show() {
const {ctx} = this
ctx.body = await ctx.model.User.findById(ctx.params.id)
}
// 创建用户 POST /users
async create() {
const {ctx} = this
ctx.body = await ctx.model.User.create(ctx.request.body)
}
// 更新用户 PUT /users/:id
async update() {
const {ctx} = this
ctx.body = await ctx.model.User.findByIdAndUpdate(ctx.params.id, ctx.request.body)
}
// 删除用户 DELETE /users/:id
async destroy() {
const {ctx} = this
ctx.body = await ctx.model.User.findByIdAndRemove(ctx.params.id)
}
}
module.exports = UserController
Наконец, настройте карту маршрутов RESTful:
module.exports = app => {
const {router, controller} = app
router.resources('users', '/users', controller.user)
}
Интегрировать MySQL
Сначала убедитесь, что база данных MySQL установлена на вашем компьютере.Если это компьютер Mac, вы можете быстро установить и запустить его с помощью следующих команд:
$ brew install mysql
$ brew services start mysql # 后台启动
# 或者 mysql.server start 前台启动
$ mysql_secure_installation # 设置密码
есть официальныйegg-mysqlПлагин, вы можете подключиться к базе данных MySQL, использование очень простое:
$ npm i egg-mysql
# 或者 yarn add egg-mysql
существуетconfig/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
}
config/config.default.js
config.mysql = {
client: {
host: 'localhost',
port: '3306',
user: 'root',
password: 'root',
database: 'cms',
}
}
app.mysql
class UserService extends Service {
async find(uid) {
const user = await this.app.mysql.get('users', { id: 11 });
return { user }
}
}
ERROR 5954 nodejs.ER_NOT_SUPPORTED_AUTH_MODEError: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password'
flush privileges
npm install egg-sequelize mysql2 --save
yarn add egg-sequelize mysql2
config/plugin.js
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
}
config/config.default.js
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'example',
}
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`name` varchar(30) DEFAULT NULL COMMENT 'book name',
`created_at` datetime DEFAULT NULL COMMENT 'created time',
`updated_at` datetime DEFAULT NULL COMMENT 'updated time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='book';
model/book.js
module.exports = app => {
const { STRING, INTEGER } = app.Sequelize
const Book = app.model.define('book', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
})
return Book
}
controller/book.js
const Controller = require('egg').Controller
class BookController extends Controller {
async index() {
const ctx = this.ctx
ctx.body = await ctx.model.Book.findAll({})
}
async show() {
const ctx = this.ctx
ctx.body = await ctx.model.Book.findByPk(+ctx.params.id)
}
async create() {
const ctx = this.ctx
ctx.body = await ctx.model.Book.create(ctx.request.body)
}
async update() {
const ctx = this.ctx
const book = await ctx.model.Book.findByPk(+ctx.params.id)
if (!book) return (ctx.status = 404)
await book.update(ctx.request.body)
ctx.body = book
}
async destroy() {
const ctx = this.ctx
const book = await ctx.model.Book.findByPk(+ctx.params.id)
if (!book) return (ctx.status = 404)
await book.destroy()
ctx.body = book
}
}
module.exports = BookController
module.exports = app => {
const {router, controller} = app
router.resources('books', '/books', controller.book)
}
npm init egg --type=plugin
# 或者 yarn create egg --type=plugin
├── config
│ └── config.default.js
├── package.json
package.json
eggPlugin
{
"eggPlugin": {
"name": "myPlugin",
"dependencies": [ "registry" ],
"optionalDependencies": [ "vip" ],
"env": [ "local", "test", "unittest", "prod" ]
}
}
name
dependencies
optionalDependencies
env
-
app/extend/
request.js
response.js
egg-bcrypt
extend.js
ctx.genHash(plainText)
ctx.compare(plainText, hash)
-
app/middleware
app.js
config/config.default.js
exports.cors = { origin: '*', allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH' }
-
app.js
beforeStart
beforeStart
-
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
-
5000
ms5s
-
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ 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)
worker
all
app.runSchedule(schedulePath)
app.runSchedule
app/schedule
app.js
module.exports = app => {
app.beforeStart(async () => {
// 程序启动前确保缓存已更新
await app.runSchedule('update_cache')
})
}
model.User
Internal Server Error, real status: 500
config/plugin.js
module.exports = {
onerror: {
accepts: () => 'json',
},
};
{
"message": "Cannot read property 'find' of undefined",
"stack": "TypeError: Cannot read property 'find' of undefined\n at UserController.index (/Users/keliq/code/egg-project/app/controller/user.js:7:37)",
"name": "TypeError",
"status": 500
}
content-type
module.exports = {
onerror: {
accepts: (ctx) => {
if (ctx.get('content-type') === 'application/json') return 'json';
return 'html';
}
},
};
config/config.default.js
module.exports = {
onerror: {
errorPageUrl: '/public/error.html',
},
};
?real_status=500
egg-onerror
module.exports = {
onerror: {
all(err, ctx) {
// 在此处定义针对所有响应类型的错误处理方法
// 注意,定义了 config.all 之后,其他错误处理方法不会再生效
ctx.body = 'error'
ctx.status = 500
},
html(err, ctx) { // 处理 html hander
ctx.body = '<h3>error</h3>'
ctx.status = 500
},
json(err, ctx) { // json hander
ctx.body = {message: 'error'}
ctx.status = 500
},
},
}
-
{ "message": "Not Found" }
-
<h1>404 Not Found</h1>
config/config.default.js
module.exports = {
notfound: {
pageUrl: '/404.html',
}
}
middleware/notfound_handler.js
module.exports = () => {
return async function (ctx, next) {
await next()
if (ctx.status === 404 && !ctx.body) {
ctx.body = ctx.acceptJSON ? { error: 'Not Found' } : '<h1>Page Not Found</h1>'
}
}
}
config/config.default.js
config.middleware = ['notfoundHandler']
configWillLoad
configDidLoad
didLoad
willReady
didReady
serverDidReady
beforeClose
app.js
class AppBootHook {
constructor(app) {
this.app = app
}
configWillLoad() {
// config 文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机
// 注意:此函数只支持同步调用
}
configDidLoad() {
// 所有的配置已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
}
async didLoad() {
// 所有的配置已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
}
async willReady() {
// 所有的插件都已启动完毕,但是应用整体还未 ready
// 可以做一些数据初始化等操作,这些操作成功才会启动应用
}
async didReady() {
// 应用已经启动完毕
}
async serverDidReady() {
// http / https server 已启动,开始接受外部请求
// 此时可以从 app.server 拿到 server 的实例
}
async beforeClose() {
// 应用即将关闭
}
}
module.exports = AppBootHook
const BAR = Symbol('bar')
module.exports = {
foo(param) {}, // 扩展方法
get bar() { // 扩展属性
if (!this[BAR]) {
this[BAR] = this.get('x-bar')
}
return this[BAR]
},
}
this
-
app/extend/application.js
app
ctx.app.xxx
app/extend/context.js
-
app/extend/<request|response>.js
request
response
request
response
-
app/extend/helper.js
helper
helper
npm init egg --type=framework --registry=china
# 或者 yarn create egg --type=framework --registry=china
├── app
│ ├── extend
│ │ ├── application.js
│ │ └── context.js
│ └── service
│ └── test.js
├── config
│ ├── config.default.js
│ └── plugin.js
├── index.js
├── lib
│ └── framework.js
├── package.json
lib/framework.js
const path = require('path')
const egg = require('egg')
const EGG_PATH = Symbol.for('egg#eggPath')
class Application extends egg.Application {
get [EGG_PATH]() {
return path.dirname(__dirname)
}
}
class Agent extends egg.Agent {
get [EGG_PATH]() {
return path.dirname(__dirname)
}
}
module.exports = Object.assign(egg, {
Application,
Agent,
})
Symbol.for('egg#eggPath')
path.dirname(__dirname)
npm link # 或者 yarn link
npm link my-framework
"egg": {
"framework": "my-framework"
},
const Application = require('egg').Application
// 继承 egg 的 Application
class Enterprise extends Application {
get [EGG_PATH]() {
return '/path/to/enterprise'
}
}
const Application = require('enterprise').Application
// 继承 enterprise 的 Application
class Department extends Application {
get [EGG_PATH]() {
return '/path/to/department'
}
}
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 |
Symbol.for('egg#eggPath')
Symbol.for('egg#loader')
const path = require('path')
const egg = require('egg')
const EGG_PATH = Symbol.for('egg#eggPath')
const EGG_LOADER = Symbol.for('egg#loader')
class MyAppWorkerLoader extends egg.AppWorkerLoader {
// 自定义的 AppWorkerLoader
}
class Application extends egg.Application {
get [EGG_PATH]() {
return path.dirname(__dirname)
}
get [EGG_LOADER]() {
return MyAppWorkerLoader
}
}
- loadPlugin()
- loadConfig()
- loadAgentExtend()
- loadApplicationExtend()
- loadRequestExtend()
- loadResponseExtend()
- loadContextExtend()
- loadHelperExtend()
- loadCustomAgent()
- loadCustomApp()
- loadService()
- loadMiddleware()
- loadController()
- loadRouter()
const {AppWorkerLoader} = require('egg')
const {EggLoader} = require('egg-core')
// 如果需要改变加载顺序,则需要继承 EggLoader,否则可以继承 AppWorkerLoader
class MyAppWorkerLoader extends AppWorkerLoader {
constructor(options) {
super(options)
}
load() {
super.load()
console.log('自定义load逻辑')
}
loadPlugin() {
super.loadPlugin()
console.log('自定义plugin加载逻辑')
}
loadConfig() {
super.loadConfig()
console.log('自定义config加载逻辑')
}
loadAgentExtend() {
super.loadAgentExtend()
console.log('自定义agent extend加载逻辑')
}
loadApplicationExtend() {
super.loadApplicationExtend()
console.log('自定义application extend加载逻辑')
}
loadRequestExtend() {
super.loadRequestExtend()
console.log('自定义request extend加载逻辑')
}
loadResponseExtend() {
super.loadResponseExtend()
console.log('自定义response extend加载逻辑')
}
loadContextExtend() {
super.loadContextExtend()
console.log('自定义context extend加载逻辑')
}
loadHelperExtend() {
super.loadHelperExtend()
console.log('自定义helper extend加载逻辑')
}
loadCustomAgent() {
super.loadCustomAgent()
console.log('自定义custom agent加载逻辑')
}
loadCustomApp() {
super.loadCustomApp()
console.log('自定义custom app加载逻辑')
}
loadService() {
super.loadService()
console.log('自定义service加载逻辑')
}
loadMiddleware() {
super.loadMiddleware()
console.log('自定义middleware加载逻辑')
}
loadController() {
super.loadController()
console.log('自定义controller加载逻辑')
}
loadRouter() {
super.loadRouter()
console.log('自定义router加载逻辑')
}
}
自定义plugin加载逻辑
自定义config加载逻辑
自定义application extend加载逻辑
自定义request extend加载逻辑
自定义response extend加载逻辑
自定义context extend加载逻辑
自定义helper extend加载逻辑
自定义custom app加载逻辑
自定义service加载逻辑
自定义middleware加载逻辑
自定义controller加载逻辑
自定义router加载逻辑
自定义load逻辑