Пересматривая предыдущую статью,Настоящая статьяговорил о
- Оказание услуг
- плагин
- задача на время
- удлинение рамы
- Начать пользовательский
- Развертывание приложения
- журнал
- HttpClient
Файлы cookie и сеансы
Cookie
пройти черезctx.cookies
, мы можем легко и безопасно устанавливать и читать файлы cookie в контроллере.
class HomeController extends Controller {
async add() {
const ctx = this.ctx;
let count = ctx.cookies.get('count');
count = count ? Number(count) : 0;
ctx.cookies.set('count', ++count);
ctx.body = count;
}
async remove() {
const ctx = this.ctx;
ctx.cookies.set('count', null);
ctx.status = 204;
}
}
ctx.cookies.set(key, value, options)
Настройка файлов cookie на самом деле выполняется путем установки заголовка set-cookie в ответе HTTP.Каждый set-cookie заставляет браузер сохранять пару ключ-значение в файле cookie. Наряду с настройкой значения файла cookie протокол также поддерживает ряд параметров для настройки передачи, хранения и разрешений этого файла cookie.
-
{Number} maxAge
: Установите максимальное время хранения этой пары ключ-значение в браузере. это количество миллисекунд с текущего момента сервера. -
{Date} expires
: Установите время истечения срока действия этой пары ключ-значение.Если установлено значение maxAge, значения expires будут перезаписаны. Если ни maxAge, ни expires не установлены, срок действия файла cookie истечет по истечении сеанса браузера (обычно при закрытии браузера). -
{String} path
: укажите путь URL-адреса, по которому будет действовать пара "ключ-значение". По умолчанию используется корневой путь (/), то есть все URL-адреса под текущим доменным именем могут получить доступ к этому файлу cookie. -
{String} domain
: укажите доменное имя, на котором действует пара "ключ-значение". По умолчанию оно не настроено. Его можно настроить так, чтобы оно было доступно только для указанного доменного имени. -
{Boolean} httpOnly
: Укажите, может ли js получить доступ к паре ключ-значение, значение по умолчанию — true, и js не может получить к ней доступ. -
{Boolean} secure
: установить пару ключ-значениеПередавать только через соединения HTTPS, платформа поможет нам определить, установлено ли в настоящее время значение secure для HTTPS-соединения автоматически.
В дополнение к этим свойствам фреймворк поддерживает 3 дополнительных параметра:
-
{Boolean} overwrite
: Укажите, как обрабатывать пары ключ-значение с одним и тем же ключом, если установлено значениеtrue
, значение, установленное позже, переопределит значение, установленное ранее, в противном случае будут отправлены два заголовка ответа set-cookie. -
{Boolean} signed
: Укажите, следует ли подписывать файл cookie. Если установлено значение true, значение пары ключ-значение будет подписано одновременно с установкой пары ключ-значение и будет проверено при извлечении позже, что может помешать внешнему интерфейсу изменить это значение. По умолчанию истинно. -
{Boolean} encrypt
: Укажите, следует ли шифровать файл cookie. Если установлено значение true, значение этой пары ключ-значение будет зашифровано перед отправкой файла cookie, и клиент не сможет прочитать значение файла cookie в виде открытого текста. По умолчанию ложно.
Файл cookie подписан и не зашифрован, браузер может видеть открытый текст, js не может получить доступ и не может быть изменен клиентом (вручную).
Если вы хотите, чтобы файлы cookie могли быть доступны и изменены с помощью js на стороне браузера:
ctx.cookies.set(key, value, {
httpOnly: false,
signed: false,
});
- Из-за неопределенности реализации браузеров и других клиентов, чтобы обеспечить успешную запись файла cookie, рекомендуется перед записью закодировать значение с помощью base64 или других форм.
- Поскольку браузеры ограничивают длину файлов cookie, старайтесь не устанавливать слишком длинные файлы cookie. Обычно не более 4093 байт. Когда установленное значение файла cookie больше этого значения, платформа распечатает журнал предупреждений.
ctx.cookies.get(key, options)
При настройке файла cookie выше мы можем установить options.signed и options.encrypt для подписи или шифрования файла cookie, поэтому соответствующие параметры также должны быть переданы при получении файла cookie.
- Если при установке он указан как подписанный, а при получении не указан,Затем полученное значение не будет проверено при его получении, что может привести к фальсификации клиентом.
- Если при настройке указано как шифровать, то при получении не указывается,Тогда нельзя получить реальное значение, а зашифрованный шифротекст.
ключ cookie
Поскольку нам нужно использовать шифрование, дешифрование и проверку подписи в файлах cookie, нам нужно настроить секретный ключ для шифрования. существуетconfig/config.default.js
середина
module.exports = {
keys: 'key1,key2',
};
keys настраивается как строка, и несколько ключей могут быть настроены с помощью конфигурации, разделенной запятыми. Когда файлы cookie шифруются и расшифровываются с использованием этой конфигурации:
- Только первый ключ используется для шифрования и подписи.
- Во время расшифровки и проверки подписи ключи перебираются для расшифровки.
Если мы хотим обновить ключ файла cookie, но не хотим, чтобы файл cookie, ранее установленный в браузере пользователя, был недействительным, мы можем настроить новый ключ в начале ключей и удалить ненужный ключ через некоторое время. Вот и все.
Session
Файлы cookie часто используются в веб-приложениях.берет на себя функцию идентификации личности запрашивающего, поэтому веб-приложениеКонцепция сеанса инкапсулирована на основе файлов cookie., который специально используется для идентификации пользователя.
рама встроеннаяSessionплагин, который предоставляет намctx.session
доступа или изменить сеанс текущего пользователя.
class HomeController 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) : 1;
ctx.body = {
success: true,
posts,
};
}
}
Использование сеанса очень интуитивно понятно. Вы можете напрямую прочитать или изменить его. Если вы хотите удалить его, вы можете напрямую присвоить ему значение null:
ctx.session = null;
Особое внимание следует обратить на: следует избегать следующих ситуаций при установке атрибута сеанса (что приведет к потере поля, подробности см.koa-sessionисходный код)
- не используй
_
начало - не может быть
isNew
// ❌ 错误的用法
ctx.session._visited = 1; // --> 该字段会在下一次请求时丢失
ctx.session.isNew = 'HeHe'; // --> 为内部关键字, 不应该去更改
// ✔️ 正确的用法
ctx.session.visited = 1; // --> 此处没有问题
Реализация сеанса основана на файлах cookie. В конфигурации по умолчанию содержимое сеанса пользователя зашифровано и хранится непосредственно в поле файла cookie. Пользователь будет использовать этот файл cookie каждый раз, когда запрашивает наш веб-сайт, и мы будем его использовать. после расшифровки на стороне сервера. Конфигурация сеанса по умолчанию выглядит следующим образом:
exports.session = {
key: 'EGG_SESS',
maxAge: 24 * 3600 * 1000, // 1 天
httpOnly: true,
encrypt: true,
};
Видно, что эти параметры являются дополнением кkey
все параметры Cookie,key
Какой ключ представляет пару ключ-значение файла cookie, в которой хранится сеанс. В конфигурации по умолчанию файл cookie, в котором хранится сеанс, будет зашифрован и сохранен, и к нему нельзя получить доступ с помощью внешнего интерфейса js, что может гарантировать безопасность сеанса пользователя.
Расширенное хранилище
Сессии по умолчанию хранятся в куках, но если наш объект Session будет слишком большим, это принесет дополнительные проблемы:
- Как упоминалось ранее, браузеры обычно имеют ограничение на максимальную длину файла cookie, и когда установленная сессия слишком велика, браузер может отказаться сохранять.
- Файл cookie будет доставляться с каждым запросом.Если сессия слишком велика, с каждым запросом будет передаваться огромное количество информации о файлах cookie.
Нам просто нужно установитьapp.sessionStore
Сессия может быть сохранена в указанном хранилище.
// app.js
module.exports = app => {
app.sessionStore = {
// support promise / async
async get (key) {
// return value;
},
async set (key, value, maxAge) {
// set key to store
},
async destroy (key) {
// destroy key
},
};
};
sessionStore
Мы также можем инкапсулировать реализацию в плагины, напримерegg-session-redisЭто дает возможность хранить сеанс в Redis, На уровне приложения нам нужно только ввестиegg-redisа такжеegg-session-redisПодойдет плагин.
// plugin.js
exports.redis = {
enable: true,
package: 'egg-redis',
};
exports.sessionRedis = {
enable: true,
package: 'egg-session-redis',
};
Если вы выберете сохранение сеанса во внешнем хранилище, это означает, что система будет сильно зависеть от этого внешнего хранилища, и когда оно зависнет, мы вообще не сможем использовать функции, связанные с сеансом. Поэтому мы рекомендуем хранить в сеансе только необходимую информацию, сохранять компактность сеанса и использовать хранилище файлов cookie по умолчанию, а также не хранить в сеансе кэши на уровне пользователя.
Сессионная практика
Изменить время истечения сеанса пользователя
Хотя одним элементом в конфигурации сеанса является maxAge, он может установить только период действия сеанса глобально.Мы часто можем видеть окно параметра «Запомнить меня» на странице входа в систему некоторых веб-сайтов.Проверив его, пользователь может войти в Сессия Срок действия больше. Этот пользовательский параметр действительного времени сеанса, который мы можем передатьctx.session.maxAge=
реализовать.
const ms = require('ms');
class UserController extends Controller {
async login() {
const ctx = this.ctx;
const { username, password, rememberMe } = ctx.request.body;
const user = await ctx.loginAndGetUser(username, password);
// 设置 Session
ctx.session.user = user;
// 如果用户勾选了 `记住我`,设置 30 天的过期时间
if (rememberMe) ctx.session.maxAge = ms('30d');
}
}
Продлить срок действия сеанса пользователя
По умолчанию, когда запрос пользователя не приводит к изменению сеанса, фреймворк не будет продлевать срок действия сеанса, однако в некоторых сценариях мы хотим, чтобы пользователи продлевали срок действия своего сеанса, если они посещают наш сайт. в течение длительного времени Выведите пользователя из состояния входа в систему.
// config/config.default.js
module.exports = {
session: {
renew: true,
},
};
Обработка исключений
errorPageUrl
Атрибут errorPageUrl поддерживается в конфигурации плагина onerror.При настройке errorPageUrl, как только пользователь запрашивает исключение для HTML-страницы онлайн-приложения, он будет перенаправлен на этот адрес.
существуетconfig/config.default.js
середина
Сначала настройте статический адрес файла
// config/config.default.js
module.exports = {
static: {
prefix: '/',
dir: path.join(appInfo.baseDir, 'app/public'),
},
};
// config/config.default.js
module.exports = {
onerror: {
// 线上页面发生异常时,重定向到这个页面上
errorPageUrl: '/50x.html',
},
};
Пользовательская унифицированная обработка исключений
// config/config.default.js
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;
},
jsonp(err, ctx) {
// 一般来说,不需要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式
},
},
};
404
Платформа не рассматривает статус 404, возвращенный сервером, как исключение, но платформа предоставляет ответ по умолчанию, когда ответ равен 404, а тело сообщения не возвращается.
- Когда фреймворк определяет, что запрос требует ответа в формате JSON, возвращается часть JSON:
{ "message": "Not Found" }
- Когда фреймворк определяет, что запрос требует ответа в формате HTML, возвращается фрагмент HTML:
<h1>404 Not Found</h1>
Платформа поддерживает перенаправление ответа 404 HTML-запроса по умолчанию на указанную страницу с помощью конфигурации.
// config/config.default.js
module.exports = {
notfound: {
pageUrl: '/404.html',
},
};
Пользовательский ответ 404
В некоторых сценариях нам нужно настроить ответ сервера 404. Как и для пользовательской обработки исключений, нам нужно только добавить промежуточное программное обеспечение для единообразной обработки 404:
// app/middleware/notfound_handler.js
module.exports = () => {
return async function notFoundHandler(ctx, next) {
await next();
if (ctx.status === 404 && !ctx.body) {
if (ctx.acceptJSON) {
ctx.body = { error: 'Not Found' };
} else {
ctx.body = '<h1>Page Not Found</h1>';
}
}
};
};
Внедрить промежуточное ПО в конфигурацию:
// config/config.default.js
module.exports = {
middleware: [ 'notfoundHandler' ],
};
MySQL
egg-mysql
структура обеспечиваетegg-mysqlПлагин для доступа к базе данных MySQL. Этот плагин может получить доступ как к обычным базам данных MySQL, так и к онлайн-сервисам баз данных на основе протокола MySQL.
установка и настройка
npm i --save egg-mysql
Включите плагин:
// config/plugin.js
exports.mysql = {
enable: true,
package: 'egg-mysql',
};
существуетconfig/config.${env}.js
Настройте информацию о подключении к базе данных для каждой среды.
единый источник данных
Если нашему приложению требуется только доступ к экземпляру базы данных 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 访问
Несколько источников данных
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,
};
Как использовать:
const client1 = app.mysql.get('db1');
await client1.query(sql, values);
const client2 = app.mysql.get('db2');
await client2.query(sql, values);
Сервисный уровень
Поскольку операция доступа к базе данных MySQL относится к уровню обработки данных на веб-уровне, мы настоятельно рекомендуем, чтобы эта часть кода поддерживалась на уровне службы.
// app/service/user.js
class UserService extends Service {
async find(uid) {
// 假如 我们拿到用户 id 从数据库获取用户详细信息
const user = await this.app.mysql.get('users', { id: 11 });
return { user };
}
}
// app/controller/user.js
class UserController extends Controller {
async info() {
const ctx = this.ctx;
const userId = ctx.params.id;
const user = await ctx.service.user.find(userId);
ctx.body = user;
}
}
Как писать CRUD-операторы
Create
// 插入
const result = await this.app.mysql.insert('posts', { title: 'Hello World' }); // 在 post 表中,插入 title 为 Hello World 的记录
=> INSERT INTO `posts`(`title`) VALUES('Hello World');
console.log(result);
=>
{
fieldCount: 0,
affectedRows: 1,
insertId: 3710,
serverStatus: 2,
warningCount: 2,
message: '',
protocol41: true,
changedRows: 0
}
// 判断插入成功
const insertSuccess = result.affectedRows === 1;
Read
можно использовать напрямуюget
метод илиselect
метод для получения одной или нескольких записей.select
Методы поддерживают условный запрос и настройку результатов.
const post = await this.app.mysql.get('posts', { id: 12 });
=> SELECT * FROM `posts` WHERE `id` = 12 LIMIT 0, 1;
Запрос полной таблицы
const results = await this.app.mysql.select('posts');
=> SELECT * FROM `posts`;
Условный запрос и настройка результатов
-
where
Условия запроса{ status: 'draft', author: ['author1', 'author2'] }
-
columns
имя столбца запроса['author', 'title']
-
orders
Сортировать по[['created_at','desc'], ['id','desc']]
-
limit
10
Количество запросов -
offset
0
Компенсировать
const results = await this.app.mysql.select('posts', { // 搜索 post 表
where: { status: 'draft', author: ['author1', 'author2'] }, // WHERE 条件
columns: ['author', 'title'], // 要查询的表字段
orders: [['created_at','desc'], ['id','desc']], // 排序方式
limit: 10, // 返回数据量
offset: 0, // 数据偏移量
});
=> SELECT `author`, `title` FROM `posts`
WHERE `status` = 'draft' AND `author` IN('author1','author2')
ORDER BY `created_at` DESC, `id` DESC LIMIT 0, 10;
Update
// 修改数据,将会根据主键 ID 查找,并更新
const row = {
id: 123,
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};
const result = await this.app.mysql.update('posts', row); // 更新 posts 表中的记录
=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE id = 123 ;
// 判断更新成功
const updateSuccess = result.affectedRows === 1;
// 如果主键是自定义的 ID 名称,如 custom_id,则需要在 `where` 里面配置
const row = {
name: 'fengmk2',
otherField: 'other field value', // any other fields u want to update
modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};
const options = {
where: {
custom_id: 456
}
};
const result = await this.app.mysql.update('posts', row, options); // 更新 posts 表中的记录
=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE custom_id = 456 ;
// 判断更新成功
const updateSuccess = result.affectedRows === 1;
Delete
const result = await this.app.mysql.delete('posts', {
author: 'fengmk2',
});
=> DELETE FROM `posts` WHERE `author` = 'fengmk2';
Выполнить оператор sql напрямую
Используйте запрос для выполнения допустимых операторов sql.
Мы настоятельно не рекомендуем разработчикам сплайсировать операторы sql, которые легко могут привести к sql-инъекциям! !
Если вам нужно соединить оператор sql самостоятельно, используйтеmysql.escape
метод.
const postId = 1;
const results = await this.app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);
=> update posts set hits = (hits + 1) where id = 1;
использовать транзакцию
Вообще говоря, транзакции должны соответствовать 4 условиям (ACID): Atomity (атомарность), Consistency (непротиворечивость), Isolation (изоляция), Durability (надежность).
- Атомарность: убедитесь, что все операции внутри транзакции завершены успешно, в противном случае транзакция будет прервана в момент сбоя, а предыдущие операции будут возвращены к предыдущему состоянию.
- Непротиворечивость: изменения в базе данных согласованы.
- Изоляция: транзакции независимы друг от друга и не влияют друг на друга.
- Долговечность: убедитесь, что результаты транзакции могут существовать вечно после фиксации транзакции.
Поэтому для транзакции она должна сопровождаться beginTransaction, commit или rollback, которые соответственно представляют собой начало транзакции, и откат в случае успеха и неудачи.
Ручное управление
- преимущество:
beginTransaction
,commit
илиrollback
Все они полностью контролируются разработчиком, и может быть достигнут очень тонкий контроль. - Недостатки: Много рукописного кода, не каждый может его хорошо написать. Если вы забудете об исключении и очистке, это может привести к серьезным ошибкам.
const conn = await app.mysql.beginTransaction(); // 初始化事务
try {
await conn.insert(table, row1); // 第一步操作
await conn.update(table, row2); // 第二步操作
await conn.commit(); // 提交事务
} catch (err) {
// error, rollback
await conn.rollback(); // 一定记得捕获异常后回滚事务!!
throw err;
}
Автоматический контроль: транзакция с объемом
- API:
beginTransactionScope(scope, ctx)
-
scope
: функция-генератор, в которой выполняются все SQL-операторы этой транзакции. -
ctx
: Объект контекста текущего запроса, передаваемый в ctx, может гарантировать, что даже в случае вложенности транзакций в запросе одновременно будет только одна активная транзакция.
-
const result = await app.mysql.beginTransactionScope(async conn => {
// don't commit or rollback by yourself
await conn.insert(table, row1);
await conn.update(table, row2);
return { success: true };
}, ctx); // ctx 是当前请求的上下文,如果是在 service 文件中,可以从 `this.ctx` 获取到
// if error throw on scope, will auto rollback
Выражение (буквальное)
Если вам нужно вызвать встроенные функции (или выражения) MySQL, вы можете использоватьLiteral
.
встроенные выражения
-
NOW()
: Текущее системное время базы данных, черезapp.mysql.literals.now
Получать.
await this.app.mysql.insert(table, {
create_time: this.app.mysql.literals.now,
});
=> INSERT INTO `$table`(`create_time`) VALUES(NOW())
пользовательское выражение
В следующем примере показано, как вызвать встроенный встроенный MySQLCONCAT(s1, ...sn)
Функция, сделайте строчку.
const Literal = this.app.mysql.literals.Literal;
const first = 'James';
const last = 'Bond';
await this.app.mysql.insert(table, {
id: 123,
fullname: new Literal(`CONCAT("${first}", "${last}"`),
});
=> INSERT INTO `$table`(`id`, `fullname`) VALUES(123, CONCAT("James", "Bond"))
Прогорный журнал для печати
при запуске
windows
set DEBUG=ali-rds* && npm run dev
линукс, мак
DEBUG=ali-rds* npm run dev
Sequelize
В сообществе Node.jssequelize— это широко используемая структура ORM, которая поддерживает несколько источников данных, таких как MySQL, PostgreSQL, SQLite и MSSQL.
Инициализировать проект
Установить зависимости
npm install --save egg-sequelize mysql2
существуетconfig/plugin.js
Представлен плагин egg-sequelize
exports.sequelize = {
enable: true,
package: 'egg-sequelize',
};
существуетconfig/config.default.js
Запишите конфигурацию продолжения в
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'egg-sequelize-doc-default',
};
Мы можем настроить разные адреса источников данных в разных конфигурациях среды, чтобы различать базы данных, используемые в разных средах.Например, мы можем создать новуюconfig/config.unittest.js
Файл конфигурации, напишите следующую конфигурацию, укажите базу данных, подключенную к egg-sequelize-doc-unittest во время модульного теста.
exports.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'egg-sequelize-doc-unittest',
};
Инициализировать базу данных и миграции
В ходе развития проекта каждая итерация может вносить изменения в структуру данных базы данных, как отслеживать изменения данных каждой итерации и быстро менять структуру данных в разных средах (разработка, тестирование, CI) и переключение итераций Шерстяная ткань? Тогда нам нужноMigrationsПришло время помочь нам управлять изменениями в наших структурах данных.
продолжение обеспечиваетsequelize-cliИнструменты для реализации миграций, мы также можем внедрить sequenceize-cli в проект egg.
Установить сиквелиз-кли
npm install --save-dev sequelize-cli
В проекте яйца мы хотим поместить весь контент, связанный с миграцией базы данных, вdatabase
каталог, поэтому мы создаем новый в корневом каталоге проекта.sequelizer
Конфигурационный файл:
'use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
Инициализировать файлы конфигурации и каталоги миграции
npx sequelize init:config
npx sequelize init:migrations
будет сгенерировано после выполненияdatabase/config.json
документы иdatabase/migrations
каталог, давайте изменим егоdatabase/config.json
, измените его на конфигурацию базы данных, используемую в нашем проекте:
{
"development": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-default",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "egg-sequelize-doc-unittest",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
Когда сиквелиз-кли и связанные с ним конфигурации инициализированы, мы можем начать писать первый файл миграции проекта, чтобы создать одну из наших пользовательских таблиц.
npx sequelize migration:generate --name=init-users
После реализации будетdatabase/migrations
Файл миграции создается в каталоге (${timestamp}-init-users.js
), мы модифицируем его для обработки инициализации таблицы пользователей:
'use strict';
module.exports = {
// 在执行数据库升级时调用的函数,创建 users 表
up: async (queryInterface, Sequelize) => {
const { INTEGER, DATE, STRING } = Sequelize;
await queryInterface.createTable('users', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
},
// 在执行数据库降级时调用的函数,删除 users 表
down: async queryInterface => {
await queryInterface.dropTable('users');
},
};
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all
Написать код
Сначала мы приходимapp/model/
Запишите модель пользователя в директорию:
'use strict';
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
age: INTEGER,
created_at: DATE,
updated_at: DATE,
});
return User;
};
Эта модель может быть передана в контроллер и сервисapp.model.User
илиctx.model.User
доступ, например, мы пишемapp/controller/users.js
:
// app/controller/users.js
const Controller = require('egg').Controller;
function toInt(str) {
if (typeof str === 'number') return str;
if (!str) return str;
return parseInt(str, 10) || 0;
}
class UserController extends Controller {
async index() {
const ctx = this.ctx;
const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
ctx.body = await ctx.model.User.findAll(query);
}
async show() {
const ctx = this.ctx;
ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
}
async create() {
const ctx = this.ctx;
const { name, age } = ctx.request.body;
const user = await ctx.model.User.create({ name, age });
ctx.status = 201;
ctx.body = user;
}
async update() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
const { name, age } = ctx.request.body;
await user.update({ name, age });
ctx.body = user;
}
async destroy() {
const ctx = this.ctx;
const id = toInt(ctx.params.id);
const user = await ctx.model.User.findByPk(id);
if (!user) {
ctx.status = 404;
return;
}
await user.destroy();
ctx.status = 200;
}
}
module.exports = UserController;
Сводка по инструменту
- sequelize-cli
- sequelize-autoАвтоматически экспортировать голые модели продолжения из базы данных