яйцо-от входа до запуска (Часть 1)

Egg.js

1 Настройка, создание и запуск среды

1.1 Введение

egg.js — это среда разработки приложений корпоративного уровня, основанная на node.js и koa в рамках Alibaba, которая может помочь командам разработчиков и разработчикам сократить расходы.
Основанный на koa2, es6 и es7, узел имеет более стандартизированный режим разработки, более низкую стоимость обучения, более элегантный код и меньшие затраты на обслуживание.

image.png

image.png


1.2 Окружающая среда Строительство

1. Требуется, чтобы версия nodejs была выше 8.0 и использовалась версия LTS.
2. Создайте среду яйца npm i egg-init -g / cnpm i egg-init -g (нужно установить только один раз)
3. Создайте проект
cd в каталог (обратите внимание, что каталог не должен быть на китайском языке без пробелов)

1.3 Создать

$ npm i egg-init -g
$ egg-init egg-example --type=simple   //例如:egg-init 项目名称 --type=simple
$ cd egg-example
$ npm i

1.4 Запуск проекта

npm run dev  
open localhost:7001 //一般性来说默认端口是7001

2 Введение в структуру каталогов

2.1 Структура каталогов

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app(项目开发目录)
|   ├── router.js (用于配置 URL 路由规则)
│   ├── 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

image.png

3 Маршрутизация доступа

Яйцо спроектировано в полном соответствии с лучшим шаблоном проектирования mvc.

3.1 Так что же такое mvc?

Полное название — Model View Controller, что является аббревиатурой от model-view-controller, парадигмы проектирования программного обеспечения.

Представление (view), контроллер (controller) и модель данных Model (Service) и файл конфигурации (config) в egg

3.2 Контроллер

  • app/controllerРеализовать контроллер в каталоге
// app/controller/home.js

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = 'hi, world';
  }
}

module.exports = HomeController;

Введите npm run dev Проверять http://127.0.0.1:7001вывод привет, мир

Я думаю, что контроллер — это интерфейс, который управляет вводом и выводом.

* Точно так же вы можете написать много таких js в каталоге app/controller для представления интерфейса.

3.3 Маршрутизатор

Он в основном используется для описания соответствующих отношений между URL-адресом запроса и контроллером, который конкретно отвечает за выполнение действия.app/router.jsфайл используется для унификации всех правил маршрутизации.

Теперь многие отдельные страницы имеют относительные маршруты.Если вы пишете js, вы также должны написать маршрут.

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

3.4 Модель модели данных (услуга)

Проще говоря, сервис — это уровень абстракции, используемый для инкапсуляции бизнес-логики в сложных бизнес-сценариях.Предоставление этой абстракции имеет следующие преимущества:

  • Сохраняйте логику в контроллере более лаконичной.
  • Чтобы сохранить независимость бизнес-логики, абстрагированная служба может многократно вызываться несколькими контроллерами.
  • Разделение логики и представления упрощает написание тестовых случаев.
// app/service/user.js
const Service = require('egg').Service;

class UserService extends Service {
  async addName(name) {
    const user = `你好,${name}`;
    return user;
  }
}

module.exports = UserService;
// app/controller/user.js
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    const userInfo = await ctx.service.user.addName('wjw');
    ctx.body = userInfo;
  }
}

3.5 вид в яйце

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

4 получить, отправить запрос

4.1 получить запрос

4.1.1 query

в URL?Последняя часть — это строка запроса, которая часто используется для передачи параметров в запросах типа GET. НапримерGET /search?name=egg&age=26серединаname=egg&age=26Это параметр, передаваемый пользователем. мы можем пройтиcontext.query(Для объекта) Получить тело проанализированного параметра

module.exports = app => {

class HomeController extends Controller {
  async getQuery() {
      const queryObj = this.ctx.query;
      console.log(queryObj.age);
      console.log(queryObj);
      //打印结果:{ name: 'egg', age: '26' }
    }
  }
  return SearchController;
};


Когда ключ в строке запроса повторяется,context.queryБерется только значение первого вхождения ключа, а последующие вхождения будут игнорироваться.GET /posts?category=egg&category=koaпройти черезcontext.queryПолученное значение равно{ category: 'egg' }.

4.1.2 queries

Иногда наша система позволяет пользователям передавать один и тот же ключ, напримерGET /posts?category=egg&id=1&id=2&id=3. Для таких случаев в фреймворке предусмотреныcontext.queriesобъект, этот объект также анализирует строку запроса, но вместо того, чтобы отбрасывать любые дубликаты, он помещает их все в один数组середина:

// GET /posts?category=egg&id=1&id=2&id=3
const Controller = require('egg').Controller;

class HomeController extends Controller {
  async getQueries() {
    console.log(this.ctx.queries);
    //result:
    // {
    //   category: [ 'egg' ],
    //   id: [ '1', '2', '3' ],
    // }
  }
};

context.queriesВсе вышеперечисленные ключи, если они имеют значения, также должны быть数组тип.

4.2 почтовый запрос

// 获取参数方法 post 请求


module.exports = app => {
class HomeController extends Controller {
  async postObj() {
      const queryObj = ctx.request.body;
      ctx.body = queryObj;
    }
  }
  return SearchController;
};

Но наш запрос иногда бывает get, иногда post, а иногда должен быть post request, но для удобства тестирования мы все же делаем запрос, поддерживающий как get, так и post запросы, поэтому промежуточное ПО, которое может получать параметры get и Почтовые запросы заодно - это очень нужно.

4.3 Напишите промежуточный уровень для обработки запросов на получение и отправку

4.3.1 Создайте новую папку промежуточного программного обеспечения в каталоге приложения

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

/**
 * 获取请求参数中间件
 * 可以使用ctx.params获取get或post请求参数
 */
module.exports = options => {
  return async function params(ctx, next) {
    ctx.params = {
      ...ctx.query,
      ...ctx.request.body
    }
    await next();
  };
};

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

4.3.3 Внедрение промежуточного ПО в /config/config.default.js

'use strict';
module.exports = appInfo => {
  const config = exports = {};
// 注入中间件
  config.middleware = [
    'params',
  ];
  return config;
};

4.3.4 Получение с помощью статей

/**
 * 添加文章接口
 */
'use strict';
const Service = require('egg').Service;
class ArticleService extends Service {
  async add() {
    const { ctx } = this;
    // 获取请求参数
    const {
      userId,
      title,
      content,
    } = ctx.params;
    const result = await ctx.model.Article.create({
      userId,
      title,
      content,
    });
    return result;
  }
}
module.exports = ArticleService;

4.3.5 Разрешить почтовые запросы на междоменные запросы

// config/plugin.js
exports.cors = {
  enable: true,
  package: 'egg-cors',
};
 // config/config.default.js
config.security = {
  csrf: {
    enable: false,
    ignoreJSON: true,
  },
  domainWhiteList: [ 'http://www.baidu.com' ], // 配置白名单
};

config.cors = {
  // origin: '*',//允许所有跨域访问,注释掉则允许上面 白名单 访问
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
};

* Как правило, лучше всего использовать белый список, не используйте все, чтобы разрешить междоменный доступ, это небезопасно.

5 база данных mysql

Фреймворк предоставляет плагин egg-mysql для доступа к базе данных MySQL. Этот плагин может получить доступ как к обычным базам данных MySQL, так и к онлайн-сервисам баз данных, основанным на протоколе MySQL.

5.1 Установка и настройка

Установите соответствующий плагин egg-mysql:

npm i --save egg-mysql

Включите плагин:

// config/plugin.js
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};

существует config/config.${env}.jsНастройте информацию о подключении к базе данных для каждой среды.

5.1.1 Единый источник данных

Если нашему приложению требуется только доступ к экземпляру базы данных MySQL, его можно настроить следующим образом:
Как пользоваться:

// config/config.${env}.js
exports.mysql = {
  // 单数据库信息配置
  client: {
    // host
    host: 'mysql.com',
    // 端口号
    port: '3306',
    // 用户名
    user: 'test_user',
    // 密码
    password: 'test_password',
    // 数据库名
    database: 'test',
  },
  // 是否加载到 app 上,默认开启
  app: true,
  // 是否加载到 agent 上,默认关闭
  agent: false,
};
await app.mysql.query(sql, values); // 单实例可以直接通过 app.mysql 访问

5.1.2 Несколько источников данных

Если нашему приложению требуется доступ к нескольким источникам данных MySQL, его можно настроить следующим образом:

exports.mysql = {
  clients: {
    // clientId, 获取client实例,需要通过 app.mysql.get('clientId') 获取
    db1: {
      // host
      host: 'mysql.com',
      // 端口号
      port: '3306',
      // 用户名
      user: 'test_user',
      // 密码
      password: 'test_password',
      // 数据库名
      database: 'test',
    },
    db2: {
      // host
      host: 'mysql2.com',
      // 端口号
      port: '3307',
      // 用户名
      user: 'test_user',
      // 密码
      password: 'test_password',
      // 数据库名
      database: 'test',
    },
    // ...
  },
  // 所有数据库配置的默认值
  default: {
  },
  // 是否加载到 app 上,默认开启
  app: true,
  // 是否加载到 agent 上,默认关闭
  agent: false,
};

5.2 Добавление, удаление, модификация и проверка пакетов

5.2.1 Вставить, вставить кусок данных в таблицу пользователей

const result = await this.app.mysql.insert('users', {
    name: 'wjw',
    age: 18
  })
// 判断:result.affectedRows === 1

5.2.2, запрос, запрос данных таблицы пользователей имя = Джек

const result = await this.app.mysql.select('users', {
    columns: ['id', 'name'], //查询字段,全部查询则不写,相当于查询*
    where: {
        name: 'wjw'
    }, //查询条件
    orders: [
        ['id', 'desc'] //降序desc,升序asc
    ],
    limit: 10, //查询条数
    offset: 0 //数据偏移量(分页查询使用)
  })
//判断:result.length > 0

5.2.3. Изменить, изменить возраст данных таблицы пользователей с идентификатором от 1 до 20.

const result = await this.app.mysql.update('users', {
      age: 20 //需要修改的数据
  }, {
      where: {
        id: 1
      } //修改查询条件
  });
//判断:result.affectedRows === 1

5.2.4, удалить, удалить данные таблицы пользователей name=wjw

const result = await this.app.mysql.delete('users', {
    name: 'wjw'
})

6 Использование файлов cookie

6.1 Знакомство с файлами cookie

  • Файл cookie — это переменная, хранящаяся на компьютере посетителя. Это позволяет нам обмениваться данными при доступе к одному и тому же доменному имени с помощью одного и того же браузера.
  • HTTP — это протокол без сохранения состояния. Проще говоря, когда вы просматриваете страницу, а затем переходите на другую страницу того же веб-сайта, сервер не может распознать, что это тот же браузер, посещающий тот же веб-сайт. Каждое посещение не имеет значения.

6.2 Установка и получение файлов cookie

6.2.1 Синтаксис настройки файлов cookie

ctx.cookies.set(key, value, options)

this.ctx.cookies.set('name','zhangsan');

6.2.2 Синтаксис получения файлов cookie

ctx.cookies.get(key, options)

this.ctx.cookies.get('name')

6.2.3 Очистить файлы cookie

this.ctx.cookies.set('name',null);

Или установите время истечения срока действия maxAge равным 0

6.3 Варианты параметров cookie

яйцо JS.org/en/core/COO…

ctx.cookies.set(key, value, {
  maxAge:24 * 3600 * 1000,
  httpOnly: true, // 默认情况下是正确的
  encrypt: true, // cookie在网络传输期间加密
  ctx.cookies.get('frontend-cookie', {
  encrypt: true
});

6.4 Установка китайских файлов cookie

6.4.1 Первое решение

console.log(new Buffer('hello, world!').toString('base64'));
// 转换成 base64字符串:aGVsbG8sIHdvcmxkIQ==
console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString()); // 还原 base64字符串:hello, world!

6.4.2 Второе решение

ctx.cookies.set(key, value, {
	maxAge:24 * 3600 * 1000,
	httpOnly: true, // 默认情况下是正确的
	encrypt: true, // cookie在网络传输期间进行加密
});

7 Использование сеанса

7.1 Краткое введение в сеанс

Сессия — это еще один механизм записи состояния клиента, разница в том, что куки хранятся в браузере клиента, а сессия хранится на сервере.

7.2 Рабочий процесс сеанса

Когда браузер обращается к серверу и отправляет первый запрос, сервер создаст объект сеанса, сгенерирует пару ключ-значение, аналогичную ключу, значению, а затем вернет ключ (куки) браузеру (клиенту), браузеру. в следующий раз, когда вы снова зайдете, сохраните ключ (cookie), чтобы найти соответствующий сеанс (значение).

7.3 Использование сеанса в Egg.js

Сеанс в egg.js имеет встроенные операции сеанса на основе egg-session.

7.3.1 Настройки

this.ctx.session.userinfo={
	name:'张三', 
  age:'20'
}

7.3.2 Получить

var userinfo=this.ctx.session

7.3.3 Настройки по умолчанию для сеанса

exports.session = {
  key: 'EGG_SESS',
  maxAge: 24 * 3600 * 1000, // 1 day httpOnly: true,
  encrypt: true
};

7.4 Конфигурация сеанса в config.default.js

config.session={
  key:'SESSION_ID',
  maxAge:864000,
  renew: true //延长会话有效期
}

7.5 Разница между файлом cookie и сеансом

  • Данные cookie хранятся в браузере клиента, а данные сеанса хранятся на сервере.
  • По сравнению с сеансом, cookie не имеет безопасности сеанса.Другие могут анализировать файл cookie, хранящийся локально, и выполнять подделку файла cookie.
  • Сессия будет сохранена на сервере в течение определенного периода времени. Когда количество посещений увеличится, это поднимет производительность вашего сервера.Чтобы снизить производительность сервера, вы должны использовать COOKIE.
  • Данные, хранящиеся в одном файле cookie, не могут превышать 4 КБ, и многие браузеры ограничивают сайт максимум 20 файлами cookie.

8 запланированных задач и задач с фиксированной точкой

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

Создайте новый файл js в каталоге app/schedule, каждый файл js является запланированной задачей.


### 8.1 Запланированные задачи
// app/schedule
module.exports = {
  schedule: {
    interval: '1m', // 1 分钟间隔
    type: 'all', // 指定所有的 worker 都需要执行
  },
  async task(ctx) {
    i++
    console.log(i)
  },
};

/* 注释:
	1ms -> 1毫秒
	1s -> 1秒
	1m -> 1分钟
*/

8.2 Задачи с фиксированной точкой

Задачи с фиксированной точкой (в качестве примера возьмем обновление таблицы лидеров в 5:30:00 каждый понедельник)


1. Используйте параметр cron для установки времени, параметр cron разделен на 6 частей, * означает, что все устраивает

*    *    *    *    *    *
┬    ┬    ┬    ┬    ┬    ┬
│    │    │    │    │    |
│    │    │    │    │    └ 星期 (0 - 7) (0或7都是星期日)
│    │    │    │    └───── 月份 (1 - 12)
│    │    │    └────────── 日期 (1 - 31)
│    │    └─────────────── 小时 (0 - 23)
│    └──────────────────── 分钟 (0 - 59)
└───────────────────────── 秒 (0 - 59, optional)
// app/schedule
module.exports = {
  schedule: {
    cron: '0 30 5 * * 1', //每周一的5点30分0秒更新
    type: 'all', // 指定所有的 worker 都需要执行
  },
  async task(ctx) {
    i++
    console.log(i)
  },
};

8.3 Выполнение запланированной задачи только один раз

Если для немедленного параметра установлено значение true, запланированная задача будет выполнять запланированную задачу сразу после запуска проекта.

module.exports = {
  schedule: {
    interval: '1m', // 1 分钟间隔
    type: 'all', // 指定所有的 worker 都需要执行
    immediate: true, //项目启动就执行一次定时任务
  },
  async task(ctx) {
    i++
    console.log(i)
  },
};

8.4 Закрыть задачи

Когда для параметра отключения установлено значение true, запланированная задача закрывается.

8.5 Укажите среду выполнения задачи по времени env

env: ["dev", "debug"] //该定时任务在开发环境和debug模式下才执行

9 Развертывание

9.1 Развертывание сервера

Во-первых, конечно, развернуть службу узла на вашем сервере, а затем установить ее.

На сервере должен быть предварительно установлен Node.js, а версия Node, поддерживаемая платформой, >= 8.0.0.
Фреймворк имеет встроенный яичный кластер для запуска процесса Master.Мастер имеет достаточную стабильность и больше не нуждается в использовании модулей демона процесса, таких как pm2.
В то же время фреймворк также предоставляет егг-скрипты для поддержки запуска и остановки онлайн-среды.

egg-scripts start --port=7001 --daemon --title=egg-server-showcase
  • --port=7001Номер порта, переменная окружения будет прочитана по умолчаниюprocess.env.PORT, если не передано, будет использоваться встроенный порт фреймворка7001.
  • --daemonРазрешать ли в фоновом режиме, не нужноnohup. Если вы используете Docker, рекомендуется запускать его прямо на переднем плане.
  • --env=prodРабочая среда Framework, по умолчанию будет читать переменные средыprocess.env.EGG_SERVER_ENV, если не пройдено, будет использована встроенная среда фреймворкаprod.
  • --workers=2Количество рабочих потоков фреймворка, количество рабочих приложений, эквивалентное количеству ядер ЦП, будет создано по умолчанию, что может полностью использовать ресурсы ЦП.
  • --title=egg-server-showcaseИспользуется для облегчения grep в процессе ps, по умолчаниюegg-server-${appname}.
  • --framework=yadanМожно настроить, если приложение используетpackage.json из egg.frameworkили укажите этот параметр.
  • --ignore-stderrИгнорировать ошибки во время запуска.

9.1.1 Элементы начальной конфигурации

Вы также можетеconfig.{env}.jsКонфигурация в указывает начальную конфигурацию.

// config/config.default.js

exports.cluster = {
  listen: {
    port: 7001,
    hostname: '127.0.0.1',
    // path: '/var/run/egg.sock',
  }
}

path,port,hostnameОба являются параметрами server.listen,egg-scripts и egg.startClusterПорт, переданный в методе, имеет приоритет над этой конфигурацией.
s

9.1.2 Команда остановки

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

  • --title=egg-serverИспользуется для уничтожения указанного приложения-яйца, если его не пройти, все приложения-яйца будут прекращены.
"start": "egg-scripts start --daemon --title=${进程名称}",
"stop": "egg-scripts stop --title=${进程名称}"
  • Вы также можете напрямую пройти
ps -eo "pid,command" | grep -- "--title=egg-server"

найти основной процесс иkillбросай, не надоkill -9.

Поскольку в яйце слишком много очков знаний, оно разделено на две главы.

Управляемое чтение

яйцо-от входа в онлайн (Часть 2)

тема яйца-мангуста