Оглядываясь назад на то, что было сказано в предыдущем посте,Часть 1сказал:
- Выполнение анализа процесса
- Анализ структуры каталогов
- порядок загрузки файлов
- Жизненный цикл
- Встроенные базовые объекты фреймворка*
Рабочая среда
Само веб-приложение не должно иметь состояния и иметь возможность устанавливать себя в соответствии со средой выполнения.
Укажите операционную среду
- пройти через
config/env
Спецификация файла, содержимое файла - это рабочая среда, такая какprod
- пройти через
EGG_SERVER_ENV
спецификация переменной окружения
Метод 2 используется чаще, посколькуEGG_SERVER_ENV
Переменные среды более удобны для указания рабочей среды, например запуска приложения в производственной среде:
EGG_SERVER_ENV=prod npm start
Получите среду выполнения в приложении
использоватьapp.config.env
Получать
Отличие от NODE_ENV
Среда выполнения и взаимосвязь сопоставления, поддерживаемые платформой по умолчанию (если не указано иное).EGG_SERVER_ENV
будет основываться наNODE_ENV
соответствовать)
NODE_ENV | EGG_SERVER_ENV | иллюстрировать |
---|---|---|
local | локальная среда разработки | |
test | unittest | модульный тест |
production | prod | Производственная среда |
когдаNODE_ENV
дляproduction
а такжеEGG_SERVER_ENV
Если не указано, фреймворк будетEGG_SERVER_ENV
установлен вprod
.
пользовательская среда
В обычном процессе разработки может быть больше, чем просто вышеперечисленные среды.Egg поддерживает настраиваемые среды для адаптации к вашему собственному процессу разработки.
Например, чтобы добавить в процесс разработки интегрированную тестовую среду SIT. БудуEGG_SERVER_ENV
установлен вsit
(и рекомендуем установитьNODE_ENV = production
), который загружается при запускеconfig/config.sit.js
, запустить переменную средыapp.config.env
будет установлен наsit
.
Отличие от Коа
В Коа мы проходимapp.env
выносить экологические суждения,app.env
Значение по умолчаниюprocess.env.NODE_ENV
.
В яйцеклетках (на основе яиц и кадр) единая конфигурация размещена вapp.config
, поэтому нам нужно пройтиapp.config.env
различать окружение,app.env
Больше не использую.
Конфигурация конфигурации
Платформа предоставляет мощную и расширяемую функцию настройки, которая может автоматически объединять конфигурации приложений, подключаемых модулей и сред, охватывать их последовательно и поддерживать различные конфигурации в зависимости от среды. Объединенная конфигурация доступна непосредственно изapp.config
Получать.
Яйцо выбралоКонфигурация как код, изменения конфигурации также должны быть опубликованы после проверки. Сам пакет приложения может быть развернут в нескольких средах, просто нужно указать рабочую среду.
Конфигурация с несколькими средами
Платформа поддерживает загрузку конфигурации в соответствии со средой и определяет файлы конфигурации для нескольких сред.
config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js
config.default.js
В качестве файла конфигурации по умолчанию все среды будут загружать этот файл конфигурации, как правило, в качестве файла конфигурации по умолчанию для среды разработки.
при указании окруженияСоответствующий файл конфигурации будет загружен одновременно, и конфигурация с тем же именем файла конфигурации по умолчанию будет перезаписана.. Например, среда prod будет загруженаconfig.prod.js
а такжеconfig.default.js
документ,config.prod.js
будет охватыватьconfig.default.js
одноименная конфигурация.
Написание конфигурации
Файл конфигурации возвращает объектный объект, который может переопределить некоторые конфигурации фреймворка.Приложение также может поместить сюда свою собственную бизнес-конфигурацию для удобства управления.app.config
.
экспортировать нотацию объекта
// 配置 logger 文件的目录,logger 默认配置由框架提供
module.exports = {
logger: {
dir: '/home/admin/logs/demoapp',
},
};
Файл конфигурации также может возвращать функцию, которая принимаетappInfo
параметр
// 将 logger 目录放到代码目录下
const path = require('path');
module.exports = appInfo => {
return {
logger: {
dir: path.join(appInfo.baseDir, 'logs'),
},
};
};
Встроенные appInfos:
appInfo | иллюстрировать |
---|---|
pkg | package.json |
name | Имя приложения, такое же, как pkg.name |
baseDir | каталог приложений кода |
HOME | Каталог пользователя, например учетная запись администратора, /home/admin |
root | Корневой каталог приложения — это baseDir только в локальных средах и средах unittest и HOME для других. |
appInfo.root
Это элегантная адаптация, например, в серверной среде, которую мы будем использовать./home/admin/logs
Как каталог журнала, и вы не хотите загрязнять каталог пользователя во время локальной разработки, эта адаптация является хорошим решением этой проблемы.
Объединить правила
Объединенное использование конфигурацииextend2модуль для глубокого копирования
const a = {
arr: [ 1, 2 ],
};
const b = {
arr: [ 3 ],
};
extend(true, a, b);
// => { arr: [ 3 ] }
// 框架直接覆盖数组而不是进行合并。
Результат конфигурации
Фреймворк выгружает окончательную объединенную конфигурацию вrun/application_config.json
(рабочий процесс) иrun/agent_config.json
(процесс агента), можно использовать для анализа проблемы.
ПО промежуточного слоя
Egg реализован на основе Koa, поэтому форма промежуточного программного обеспечения Egg такая же, как и у Koa, оба основаны на модели лукового кольца. Каждый раз, когда мы пишем промежуточное ПО, это похоже на заворачивание луковицы.
написать промежуточное ПО
написание
Написание содержимого промежуточного программного обеспечения
// app/middleware/gzip.js
const isJSON = require('koa-is-json');
const zlib = require('zlib');
async function gzip(ctx, next) {
await next();
// 后续中间件执行完成后将响应体转换成 gzip
let body = ctx.body;
if (!body) return;
if (isJSON(body)) body = JSON.stringify(body);
// 设置 gzip body,修正响应头
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding', 'gzip');
}
Промежуточное ПО фреймворка написано точно так же, как промежуточное ПО Коа, поэтому любое промежуточное ПО Коа может напрямую использоваться фреймворком.
настроить
Вообще говоря, промежуточное ПО также будет иметь свою собственную конфигурацию.
Условимся, что middleware — это отдельный файл, помещаемый в директорию app/middleware, он должен экспортировать обычную функцию, которая принимает два параметра:
- варианты: элементы конфигурации промежуточного программного обеспечения, фреймворк будет
app.config[${middlewareName}]
Передано, поэтому вы можете напрямую получить конфигурацию промежуточного программного обеспечения в файле конфигурации. - app: экземпляр текущего приложения
Сделайте простую оптимизацию вышеуказанного промежуточного программного обеспечения gzip, чтобы оно поддерживало указание сжатия gzip только тогда, когда размер тела превышает настроенный порог.app/middleware
Создайте новый файл в каталогеgzip.js
// app/middleware/gzip.js
const isJSON = require('koa-is-json');
const zlib = require('zlib');
module.exports = options => {
return async function gzip(ctx, next) {
await next();
// 后续中间件执行完成后将响应体转换成 gzip
let body = ctx.body;
if (!body) return;
// 支持 options.threshold
if (options.threshold && ctx.length < options.threshold) return;
if (isJSON(body)) body = JSON.stringify(body);
// 设置 gzip body,修正响应头
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding', 'gzip');
};
};
Использовать промежуточное ПО
После того, как промежуточное программное обеспечение написано, нам также нужно установить его вручную
Используйте промежуточное ПО в приложении
существуетconfig.default.js
Добавьте следующую конфигурацию, чтобы завершить открытие и настройку промежуточного программного обеспечения.
module.exports = {
// 配置需要的中间件,数组顺序即为中间件的加载顺序
middleware: [ 'gzip' ],
// 配置 gzip 中间件的配置
gzip: {
threshold: 1024, // 小于 1k 的响应体不压缩
},
// options.gzip.threshold
};
Эта конфигурация в конечном итоге будет объединена вapp.config.appMiddleware
Использование промежуточного программного обеспечения в фреймворках и плагинах
Фреймворки и плагины не поддерживаются вconfig.default.js
среднее соответствиеmiddleware
, вам необходимо сделать следующее:
// app.js
module.exports = app => {
// 在中间件最前面统计请求时间
app.config.coreMiddleware.unshift('report');
};
// app/middleware/report.js
module.exports = () => {
return async function (ctx, next) {
const startTime = Date.now();
await next();
// 上报请求时间
reportTime(Date.now() - startTime);
}
};
ПО промежуточного слоя, определяемое прикладным уровнем (app.config.appMiddleware
) и промежуточное ПО по умолчанию (app.config.coreMiddleware
) будет загружен загрузчиком и смонтирован вapp.middleware
начальство.
Использовать промежуточное ПО в маршрутизаторе
Промежуточное ПО, настроенное двумя указанными выше способами, является глобальным и будет обрабатывать каждый запрос.
Если вы хотите применить эффект только для одного маршрута, вы можете напрямуюapp/router.js
создается и монтируется следующим образом:
module.exports = app => {
const gzip = app.middleware.gzip({ threshold: 1024 });
app.router.get('/needgzip', gzip, app.controller.handler);
};
Промежуточное ПО по умолчанию для фреймворка
В дополнение к промежуточному ПО, загружаемому прикладным уровнем, сам фреймворк и другие плагины также загружают много промежуточного ПО.
Все эти элементы конфигурации со встроенным промежуточным ПО модифицируются путем изменения одноименного элемента конфигурации промежуточного ПО в конфигурации.
В промежуточном программном обеспечении, которое поставляется с фреймворком, есть промежуточное программное обеспечение bodyParser (загрузчик фреймворка изменит различные разделители в имени файла на имена переменных в случае верблюда), мы хотим изменить конфигурацию bodyParser, просто вconfig/config.default.js
написано на
module.exports = {
bodyParser: {
jsonLimit: '10mb',
},
};
Промежуточное ПО с использованием Koa
Экосистему промежуточного программного обеспечения Koa можно легко внедрить во фреймворк.
Возьмите koa-compress в качестве примера при использовании в Koa:
const koa = require('koa');
const compress = require('koa-compress');
const app = koa();
const options = { threshold: 2048 };
app.use(compress(options));
Egg
// app/middleware/compress.js
// koa-compress 暴露的接口(`(options) => middleware`)和框架对中间件要求一致
module.exports = require('koa-compress');
настроить промежуточное ПО
// config/config.default.js
module.exports = {
middleware: [ 'compress' ],
compress: {
threshold: 2048,
},
}
Общая конфигурация
- enable: контролирует, включено ли промежуточное ПО.
- match: установите, что только запросы, соответствующие определенным правилам, будут проходить через это промежуточное ПО.
- игнорировать: настроить запросы, соответствующие определенным правилам, чтобы они не проходили через это промежуточное ПО.
соответствовать и игнорировать
Параметры, поддерживаемые match и ignore, одинаковы, но функции полностью противоположны, Match и ignore не могут быть настроены одновременно.
module.exports = {
gzip: {
match: '/static',
},
};
module.exports = {
gzip: {
match(ctx) {
// 只有 ios 设备才开启
const reg = /iphone|ipad|ipod/i;
return reg.test(ctx.get('user-agent'));
},
},
};
правила соответствия, взятые изegg-path-matching
const pathMatching = require('egg-path-matching');
const options = {
ignore: '/api', // string will use parsed by path-to-regexp
// support regexp
ignore: /^\/api/,
// support function
ignore: ctx => ctx.path.startsWith('/api'),
// support Array
ignore: [ ctx => ctx.path.startsWith('/api'), /^\/foo$/, '/bar'],
// support match or ignore
match: '/api',
};
маршрутизация
Маршрутизатор в основном используется для описания соответствующих отношений между URL-адресом запроса и контроллером, который отвечает за выполнение действия.app/router.js
файл используется для унификации всех правил маршрутизации.
Как определить маршрутизатор
-
app/router.js
Определите правила маршрутизации URL внутри
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};
-
app/controller
Реализовать контроллер в каталоге
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`,
};
}
}
Подробное описание маршрутизатора
Ниже приводится полное определение маршрутизации, параметры могут свободно выбираться в соответствии с различными сценариями:
router.verb('path-match', app.controller.action);
router.verb('router-name', 'path-match', app.controller.action);
router.verb('path-match', middleware1, ..., middlewareN, app.controller.action);
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);
Полное определение маршрутизации в основном включает 5 основных частей:
- verb — действие, инициируемое пользователем, поддерживает все методы HTTP, такие как get, post и т. д., которые будут подробно объяснены с примерами позже.
-
router.head
- HEAD -
router.options
- OPTIONS -
router.get
- GET -
router.put
- PUT -
router.post
- POST -
router.patch
- PATCH -
router.delete
- DELETE -
router.del
- Поскольку удаление является зарезервированным словом, предоставляется псевдоним для метода удаления. -
router.redirect
- URL-адрес может быть перенаправлен, например, корневой каталог, который мы используем чаще всего, может направить корневой каталог, к которому обращается пользователь, на домашнюю страницу.
-
- router-name устанавливает псевдоним для маршрута и может генерировать URL-адреса с помощью вспомогательных функций pathFor и urlFor, предоставляемых Helper. (необязательный)
- path-match - маршрутизировать пути URL
- middleware1...middlewareN — в маршрутизаторе можно настроить несколько промежуточных программ. (Необязательно) укажите, что URL-адрес обрабатывается только этим ПО промежуточного слоя.
- controller — указывает конкретный контроллер, на который сопоставляется маршрут.Контроллер можно записать двумя способами:
-
app.controller.user.fetch
- Укажите конкретный контроллер напрямую -
'user.fetch'
- может быть сокращено до строковой формы
-
Меры предосторожности
- В определении маршрутизатора он может поддерживать несколько тандемных исполнений промежуточного программного обеспечения.
- Контроллер должен быть определен в каталоге app/controller.
- Файл также может содержать несколько определений контроллера.При определении маршрута вы можете передать
${fileName}.${functionName}
способ указать соответствующий контроллер. - Контроллер поддерживает подкаталоги, при определении маршрутов можно передать
${directoryName}.${fileName}.${functionName}
способ сформулировать соответствующий Контроллер.
demo
// 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
};
Определение URL-адреса RESTful
при условииapp.resources('routerName', 'pathMatch', controller)
Быстро создавайте структуры маршрутизации CRUD на пути.
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.resources('posts', '/api/posts', controller.posts);
router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
};
Приведенный выше код/posts
Набор структур пути CRUD развертывается на пути, и соответствующий контроллерapp/controller/posts.js
Далее нужно простоposts.js
Достаточно реализовать внутри соответствующую функцию.
Method | Path | Route Name | Controller.Action |
---|---|---|---|
GET | /posts | posts | app.controllers.posts.index |
GET | /posts/new | new_post | app.controllers.posts.new |
GET | /posts/:id | post | app.controllers.posts.show |
GET | /posts/:id/edit | edit_post | app.controllers.posts.edit |
POST | /posts | posts | app.controllers.posts.create |
PUT | /posts/:id | post | app.controllers.posts.update |
DELETE | /posts/:id | post | app.controllers.posts.destroy |
// app/controller/posts.js
exports.index = async () => {};
exports.new = async () => {};
exports.create = async () => {};
exports.show = async () => {};
exports.edit = async () => {};
exports.update = async () => {};
exports.destroy = async () => {};
Если какие-то из этих методов нам не нужны, их не нужно реализовывать в posts.js, чтобы соответствующий URL-путь не регистрировался в Router.
роутер в действии
сбор параметров
Метод строки запроса
ctx.query.xxx
// 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}`;
};
// curl http://127.0.0.1:7001/search?name=egg
наименование параметра
/user/:id/:name
=> ctx.params.xxx
// 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}`;
};
// curl http://127.0.0.1:7001/user/123/xiaoming
Получить сложные параметры
Маршрутизация также поддерживает определение обычных правил, которые могут более гибко получать параметры:
// app/router.js
module.exports = app => {
app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};
// app/controller/package.js
exports.detail = async ctx => {
// 如果请求 URL 被正则匹配, 可以按照捕获分组的顺序,从 ctx.params 中获取。
// 按照下面的用户请求,`ctx.params[0]` 的 内容就是 `egg/1.0.0`
ctx.body = `package:${ctx.params[0]}`;
};
// curl http://127.0.0.1:7001/package/egg/1.0.0
Получить содержимое формы
// app/router.js
module.exports = app => {
app.router.post('/form', app.controller.form.post);
};
// app/controller/form.js
exports.post = async ctx => {
ctx.body = `body: ${JSON.stringify(ctx.request.body)}`;
};
Инициирование запроса POST непосредственно здесь приведет к сообщению об ошибке.
Вышеупомянутая проверка связана с тем, что подключаемый модуль безопасности egg-security встроен в платформу, которая обеспечивает некоторые методы обеспечения безопасности по умолчанию, а подключаемый модуль безопасности платформы включен по умолчанию. меры предосторожности, можно напрямую установить для атрибута enable этого элемента значение false.
временно отключить csrf
exports.security = {
csrf: false
};
проверка формы
npm i -S egg-validate
// config/plugin.js
module.exports = {
// had enabled by egg
// static: {
// enable: true,
// }
validate: {
enable: true,
package: 'egg-validate',
},
};
// app/router.js
module.exports = app => {
app.router.resources('/user', app.controller.user);
};
// app/controller/user.js
const createRule = {
username: {
type: 'email',
},
password: {
type: 'password',
compare: 're-password',
},
};
exports.create = async ctx => {
// 如果校验报错,会抛出异常
ctx.validate(createRule);
ctx.body = ctx.request.body;
};
перенаправить
Внутреннее перенаправление
// app/router.js
module.exports = app => {
app.router.get('index', '/home/index', app.controller.home.index);
app.router.redirect('/', '/home/index', 303); // 访问 / 自动重定向到 /home/index
};
// app/controller/home.js
exports.index = async ctx => {
ctx.body = 'hello controller';
};
Внешнее перенаправление
exports.index = async ctx => {
const type = ctx.query.type;
const q = ctx.query.q || 'nodejs';
if (type === 'bing') {
ctx.redirect(`http://cn.bing.com/search?q=${q}`);
} else {
ctx.redirect(`https://www.google.co.kr/search?q=${q}`);
}
};
Контроллер
Контроллер отвечает за анализ ввода пользователя и возврат соответствующего результата после обработки.
Framework рекомендует основной уровень контроллераОбработать (проверить, преобразовать) параметры запроса пользователя, а затем вызвать соответствующий сервисный метод для обработки бизнес-процесса, инкапсулировать и вернуть бизнес-результат после получения бизнес-результата:
- Получить параметры запроса, переданные пользователем через HTTP
- Калибровка и параметры сборки
- Вызвать Сервис для бизнес-обработки и при необходимости обработать и преобразовать возвращенный результат Сервиса, чтобы адаптировать его к потребностям пользователей.
- Отвечать пользователю по HTTP
Как написать контроллер
Все файлы контроллера должны быть помещены в каталог app/controller, который может поддерживать многоуровневые каталоги и может быть доступен через каскадное имя каталога при доступе.
Класс контроллера (рекомендуется)
// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
async create() {
const { ctx } = this;
ctx.body = 'PostController';
ctx.status = 201;
}
}
module.exports = PostController;
Определенный выше метод может быть расположен в маршруте через app.controller на основе имени файла и имени метода.
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.post('createPost', '/api/posts', controller.post.create);
}
Контроллер поддерживает многоуровневые каталоги, например, если мы поместим приведенный выше код контроллера вapp/controller/sub/post.js
в то
app.router.post('createPost', '/api/posts', app.controller.sub.post.create);
Введение атрибутаОпределенный класс Controller будет создавать экземпляр совершенно нового объекта, когда каждый запрос обращается к серверу, а класс Controller в проекте наследуется отegg.Controller
, будут висеть следующие свойстваthis
начальство.
-
this.ctx
: Экземпляр объекта контекста текущего запроса, через который мы можем получить различные удобные свойства и методы, инкапсулированные фреймворком для обработки текущего запроса. -
this.app
: Экземпляр текущего объекта приложения Application, через который мы можем получить глобальные объекты и методы, предоставляемые фреймворком. -
this.service
: Служба, определяемая приложением, через которую мы можем получить доступ к абстрактному бизнес-уровню, который эквивалентен this.ctx.service . -
this.config
: элементы конфигурации для среды выполнения приложения. -
this.logger
: объект логгера с четырьмя методами (debug
,info
,warn
,error
), Representing four different levels of print logs, and use the same effects as described in the context of the logger, but by the logger object record log, the log will be added in front of the print log file path to quickly locate log print позиция.
Базовый класс пользовательского контроллера
// 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) {
msg = msg || 'not found';
this.ctx.throw(404, msg);
}
}
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); // 使用基类的方法
}
}
Метод контроллера
Каждый Controller — это асинхронная функция, входным параметром которой является экземпляр запрошенного контекстного объекта Context, через который мы можем получить различные удобные свойства и методы, инкапсулированные фреймворком.
// app/controller/post.js
exports.create = async ctx => {
const createRule = {
title: { type: 'string' },
content: { type: 'string' },
};
// 校验参数
ctx.validate(createRule);
// 组装参数
const author = ctx.session.userId;
const req = Object.assign(ctx.request.body, { author });
// 调用 service 进行业务处理
const res = await ctx.service.post.create(req);
// 设置响应内容和响应状态码
ctx.body = { id: res.id };
ctx.status = 201;
};
Получить параметры HTTP-запроса
query
class PostController extends Controller {
async listPosts() {
const query = this.ctx.query;
// {
// category: 'egg',
// language: 'node',
// }
}
}
Когда ключ в строке запроса повторяется,ctx.query
Берется только значение первого вхождения ключа, а последующие вхождения будут игнорироваться.
queries
Иногда наша система позволяет пользователям передавать один и тот же ключ, напримерGET /posts?category=egg&id=1&id=2&id=3
.
стойка предоставляетсяctx.queries
объект, который также анализирует строку запроса, но вместо того, чтобы отбрасывать любые дубликаты, он помещает их все в массив.
// GET /posts?category=egg&id=1&id=2&id=3
class PostController extends Controller {
async listPosts() {
console.log(this.ctx.queries);
// {
// category: [ 'egg' ],
// id: [ '1', '2', '3' ],
// }
}
}
Router params
Параметры также могут быть объявлены на маршрутизаторе, и эти параметры могут быть переданы черезctx.params
полученный.
// app.get('/projects/:projectId/app/:appId', 'app.listApp');
// GET /projects/1/app/2
class AppController extends Controller {
async listApp() {
assert.equal(this.ctx.params.projectId, '1');
assert.equal(this.ctx.params.appId, '2');
}
}
body
рама встроеннаяbodyParserПромежуточное ПО для анализа тела запроса этих двух форматов в объект и его монтирования вctx.request.body
начальство.
Вообще говоря, элемент конфигурации, который мы чаще всего настраиваем, заключается в изменении максимальной длины, разрешенной во время синтаксического анализа, которую можно установить вconfig/config.default.js
Переопределите значение по умолчанию для фреймворка в .
module.exports = {
bodyParser: {
jsonLimit: '1mb',
formLimit: '1mb',
},
};
Если тело запроса пользователя превышает максимальную длину синтаксического анализа, которую мы настроили, будет выдан код состояния.413
Исключение (Request Entity Too Large), если тело запроса пользователя не может быть проанализировано (неправильный JSON), будет выдан код состояния.400
Исключение (неверный запрос).
Распространенная ошибка — ставитьctx.request.body
а такжеctx.body
путаница, последнее на самом делеctx.response.body
сокращение для .
Получить загруженные файлы
Обычно браузеры используютMultipart/form-data
Для отправки файлов в формате фреймворк поддерживает получение файлов, загруженных пользователями, через встроенный плагин Multipart.
Режим файла:
Включите файловый режим в файле конфигурации:
// config/config.default.js
exports.multipart = {
mode: 'file',
};
Чтобы загрузить/получить файлы:
Чтобы загрузить/получить один файл:
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file: <input name="file" type="file" />
<button type="submit">Upload</button>
</form>
Соответствующий внутренний код выглядит следующим образом:
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');
module.exports = class extends Controller {
async upload() {
const { ctx } = this;
const file = ctx.request.files[0];
const name = 'egg-multipart-test/' + path.basename(file.filename);
let result;
try {
// 处理文件,比如上传到云端
result = await ctx.oss.put(name, file.filepath);
} finally {
// 需要删除临时文件
await fs.unlink(file.filepath);
}
ctx.body = {
url: result.url,
// 获取所有的字段值
requestBody: ctx.request.body,
};
}
};
Загрузить/получить несколько файлов:
Для нескольких файлов мы используемctx.request.files
Свойства просматриваются и затем обрабатываются отдельно:
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file1: <input name="file1" type="file" />
file2: <input name="file2" type="file" />
<button type="submit">Upload</button>
</form>
Соответствующий внутренний код:
// app/controller/upload.js
const Controller = require('egg').Controller;
const fs = require('mz/fs');
module.exports = class extends Controller {
async upload() {
const { ctx } = this;
console.log(ctx.request.body);
console.log('got %d files', ctx.request.files.length);
for (const file of ctx.request.files) {
console.log('field: ' + file.fieldname);
console.log('filename: ' + file.filename);
console.log('encoding: ' + file.encoding);
console.log('mime: ' + file.mime);
console.log('tmp filepath: ' + file.filepath);
let result;
try {
// 处理文件,比如上传到云端
result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
} finally {
// 需要删除临时文件
await fs.unlink(file.filepath);
}
console.log(result);
}
}
};
Чтобы обеспечить безопасность загрузки файлов, платформа ограничивает поддерживаемые форматы файлов.По умолчанию платформа поддерживает белые списки следующим образом:
// images
'.jpg', '.jpeg', // image/jpeg
'.png', // image/png, image/x-png
'.gif', // image/gif
'.bmp', // image/bmp
'.wbmp', // image/vnd.wap.wbmp
'.webp',
'.tif',
'.psd',
// text
'.svg',
'.js', '.jsx',
'.json',
'.css', '.less',
'.html', '.htm',
'.xml',
// tar
'.zip',
'.gz', '.tgz', '.gzip',
// video
'.mp3',
'.mp4',
'.avi',
Пользователи могут использоватьconfig/config.default.js
config, чтобы добавить поддерживаемые расширения файлов или переопределить весь белый список.
- Добавлены поддерживаемые расширения файлов
module.exports = {
multipart: {
fileExtensions: [ '.apk' ] // 增加对 apk 扩展名的文件支持
},
};
- Переопределить весь белый список
module.exports = {
multipart: {
whitelist: [ '.png' ], // 覆盖整个白名单,只允许上传 '.png' 格式
},
};
смотрите подробностиEgg-Multipart
header
Помимо получения параметров из URL-адреса и тела запроса, многие параметры передаются через заголовки запроса.
-
ctx.headers
,ctx.header
,ctx.request.headers
,ctx.request.header
: Эти методы эквивалентны, все они полученывесь объект заголовка. -
ctx.get(name)
,ctx.request.get(name)
: Получить значение поля в заголовке запроса. Если поле не существует, будет возвращена пустая строка. - Мы рекомендуем использовать
ctx.get(name)
вместоctx.headers['name']
, потому что первый обрабатывает регистр автоматически.
Cookie
пройти черезctx.cookies
Мы можем сделать удобные, безопасные настройки и прочитать файлы cookie в Контроллере.
class CookieController extends Controller {
async add() {
const ctx = this.ctx;
const count = ctx.cookies.get('count');
count = count ? Number(count) : 0;
ctx.cookies.set('count', ++count);
ctx.body = count;
}
async remove() {
const ctx = this.ctx;
const count = ctx.cookies.set('count', null);
ctx.status = 204;
}
}
Session
С помощью файлов cookie мы можем настроить сеанс для каждого пользователя для хранения информации, связанной с его личностью.Эта информация будет зашифрована и сохранена в файле cookie, чтобы обеспечить сохранение личности пользователя при перекрестном запросе.
рама встроеннаяSessionПлагин предоставляет нам ctx.session для доступа или изменения Session текущего пользователя.
class PostController extends Controller {
async fetchPosts() {
const ctx = this.ctx;
// 获取 Session 上的内容
const userId = ctx.session.userId;
const posts = await ctx.service.post.fetch(userId);
// 修改 Session 的值
ctx.session.visited = ctx.session.visited ? ++ctx.session.visited : 1;
ctx.body = {
success: true,
posts,
};
}
}
Использование сеанса очень интуитивно понятно. Вы можете напрямую прочитать или изменить его. Если вы хотите удалить его, вы можете напрямую назначить его какnull
:
class SessionController extends Controller {
async deleteSession() {
this.ctx.session = null;
}
};
настроить
module.exports = {
key: 'EGG_SESS', // 承载 Session 的 Cookie 键值对名字
maxAge: 86400000, // Session 的最大有效时间
};
проверка параметров
с помощьюValidateПлагин предоставляет удобный механизм проверки параметров, который помогает нам выполнять различные сложные проверки параметров.
Поскольку это подключаемый модуль, необходимо выполнить регистрацию подключаемого модуля:
// config/plugin.js
exports.validate = {
enable: true,
package: 'egg-validate',
};
пройти черезctx.validate(rule, [body])
Проверьте параметры напрямую:
this.ctx.validate({
title: { type: 'string' },
content: { type: 'string' },
});
Когда проверка ненормальна, исключение будет выдано напрямую, а код состояния исключения422
(Необрабатываемый объект), поле ошибок содержит подробную информацию об ошибке проверки. Если вы хотите самостоятельно обрабатывать проверенные исключения, вы можете передатьtry catch
Сними сам.
class PostController extends Controller {
async create() {
const ctx = this.ctx;
try {
ctx.validate(createRule);
} catch (err) {
ctx.logger.warn(err.errors);
ctx.body = { success: false };
return;
}
}
};
Проверить правила
Проверка параметров пройденаParameterГотово, поддерживаемые правила проверки можно найти в документации этого модуля.
в состоянии пройтиapp.validator.addRule(type, check)
способ добавления пользовательских правил.
// app.js
app.validator.addRule('json', (rule, value) => {
try {
JSON.parse(value);
} catch (err) {
return 'must be json string';
}
});
позвоните в сервис
Любой метод любой службы может быть вызван в контроллере, а служба загружается отложенно, и инфраструктура создает ее экземпляр только при доступе к ней.
Отправить HTTP-ответ
установить статус
ctx.status = 201;
установить тело
Большая часть данных отправляется инициатору запроса через тело.Как и тело в запросе, тело, отправленное в ответе, также должно иметь соответствующий Content-Type, чтобы сообщить клиенту, как анализировать данные.
ctx.body
даctx.response.body
сокращение от , неctx.request.body
Смущенный.
class ViewController extends Controller {
async show() {
this.ctx.body = {
name: 'egg',
category: 'framework',
language: 'Node.js',
};
}
async page() {
this.ctx.body = '<html><h1>Hello</h1></html>';
}
}
Из-за потоковой природы Node.js у нас все еще есть много сценариев, в которых нам нужно возвращать ответы через Stream, например, возвращать большой файл, а прокси-сервер напрямую возвращает восходящий контент.Фреймворк также поддерживает прямую настройку тела как поток, который будет обрабатываться одновременно с событиями ошибок в этом потоке.
class ProxyController extends Controller {
async proxy() {
const ctx = this.ctx;
const result = await ctx.curl(url, {
streaming: true,
});
ctx.set(result.header);
// result.res 是一个 stream
ctx.body = result.res;
}
};
установить заголовок
пройти черезctx.set(key, value)
метод может установить заголовок ответа,ctx.set(headers)
Установите несколько заголовков.
перенаправить
Фреймворк переопределяет родной koa с помощью плагина безопасности.ctx.redirect
Реализовано для обеспечения более безопасного перенаправления.
-
ctx.redirect(url)
Если его нет в настроенном белом списке доменного имени, перенаправление запрещено. -
ctx.unsafeRedirect(url)
Перейти напрямую, не оценивая доменное имя. Как правило, его использовать не рекомендуется. Используйте его после четкого понимания возможных рисков.
Если пользователь используетctx.redirect
метод, вам необходимо выполнить следующую настройку в файле конфигурации приложения:
// config/config.default.js
exports.security = {
domainWhiteList:['.domain.com'], // 安全白名单,以 . 开头
};
Если пользователь не настраиваетdomainWhiteList
илиdomainWhiteList
Если массив пуст, все запросы на переход будут выпущены по умолчанию, что эквивалентноctx.unsafeRedirect(url)