Handle.js
Handle — это промежуточный слой, основанный на koa и sequelize, который позволяет вам сосредоточиться только на логике интерфейса.
API Documentation:
Установка:
npm i @ntbl/handle --save
Использование:
import Handle from '@ntbl/handle'
// Импорт модели sequelize
import { Article } from '../models/db'
// Передача статьи в Handle и создание экземпляра
const article = new Handle(Article)
// Создание промежуточного программного обеспечения для запроса всех данных текущей модели
const find = article.findAll()
// Привязка к маршруту
router.get('/article/find', find)
Загрузчик:
Загрузчик объединяет импорт моделей sequelize и создание экземпляров Handle.
// Предыдущий способ написания
const Article sequelize.import(__dirname + './article')
const article = new Handle(Article)
// Использование загрузчика после
// Обратите внимание, что вам всё ещё нужно передать sequelize
// Внутренний метод использует sequelize.import() для загрузки файла модели
const article = Handle.load(sequelize, __dirname + './article')
Кроме того, поддерживается пакетная загрузка, что делает процесс более эффективным.
// Прохождение через все файлы .js в указанном каталоге (по умолчанию игнорируются index.js и файлы, начинающиеся с _) и их загрузка
// Возвращает объект, где ключ — это имя файла, а значение — экземпляр Handle
// Также возвращает объект с именем _models, содержащий экземпляры моделей sequelize
const db = Handle.loadAll(sequelize, __dirname, {
// Помимо объекта параметров конструктора Handle,
// также поддерживает правила сопоставления (поддерживает запись glob)
rule: '/**/!(index|_)*.js', // По умолчанию
})
Методы экземпляра:
Handle имеет большинство методов моделей sequelize, которые можно разделить на две категории.
Первая категория называется «быстрыми методами». После вызова они сразу же генерируют асинхронную функцию (интерфейсную функцию), которую можно напрямую привязать к маршруту, без необходимости писать строку кода.
router.get('/article/find', article.findAll())
Вторая категория называется «методы процесса», после вызова они возвращают только данные, которые необходимо обработать с помощью метода process
экземпляра.
rawFindOne, rawFindAll, rawFindById, rawFindOrCreate, rawFindAndCountAll, rawFindCreateFind, rawCount, rawMax, rawMin, rawSum, rawCreate, rawBulkCreate, rawUpdate, rawDestroy, rawIncrement, rawDecrement.
const find = artcile.process(async function (d) {
// Проверка данных
// Опущено
// Запрос пользователя
const userData = await user
.where('username', 'password')
.rawFindOne()
// Запрос статей текущего пользователя
const result = await this
.where(['id', userData.id])
.rawFindAll()
// Возврат рекомендованных статей
return result.filter(e => e.type === 'recommend')
})
// Наконец, привязка к маршруту
router.get('/article/find', find)
Изменение метода по умолчанию:
Вы можете заметить, что быстрые методы имеют фиксированный метод запроса, мы можем изменить его следующим образом.
// Это повлияет на всё приложение
// Не передавайте прокси-серверу объект, он перезапишет значения по умолчанию
Handle.defaults.proxy.findAll.method = 'post'
// Пусть весь экземпляр действует
// Это нормально, потому что это пустой объект
article.options.proxy = {
findAll: {
method: 'post'
}
}
// Сделать текущий вызов эффективным
article
.method('post')
.findAll()
Обратите внимание, что порядок приоритета: метод > экземпляр > всё приложение, первый будет перекрывать второй.
Инструменты:
handle.js включает набор инструментов, который инкапсулирует некоторые распространённые логики интерфейса, помогая вам быстро создавать сложные интерфейсы и в полной мере использовать преимущества инкапсуляции.
Параметры интерфейса:
Метод where инструмента помогает вам более гибко обрабатывать логику параметров интерфейса и предоставляет шесть удобных способов записи предложений where.
article
// uid ☞ идентификатор пользователя
// Запросить все данные статьи указанного пользователя
.where('uid')
.findAll()
article
// Запросите данные указанной статьи пользователя, удовлетворяющие двум условиям одновременно
.where('uid', 'id')
.findAll()
article
// Запросить данные статьи с id = 1
// Обратите внимание, что необходимо передать массив
.where(['id', 1])
.findAll()
article
// Используйте псевдоним aid для запроса данных статьи
// Внешнее использование псевдонима aid, внутреннее использование id
.where(['id', '@aid'])
.findAll()
article
// Запросить данные статьи, используя id или uid
// Если ни id, ни uid не указаны, это означает отсутствие каких-либо ограничений условий, и это вернёт все данные
// Вы должны быть осторожны!
.where(['!id', '!uid'])
.findAll()
article
// Запросить данные статьи больше указанного id
.where('id >')
.findAll()
Некоторые синтаксисы Op требуют специальных динамических значений, поэтому Handle добавляет поддержку передачи массива в качестве второго элемента функции. findAll()
where 支持的所有 Op 便捷写法。
let opTag = {
'>': 'gt',
'>=': 'gte',
'<': 'lt',
'<=': 'lte',
'!=': 'ne',
'=': 'and',
'#and': 'and',
'#or': 'or',
'#gt': 'gt',
'#gte': 'gte',
'#lt': 'lt',
'#lte': 'lte',
'#ne': 'ne',
'#eq': 'eq',
'#not': 'not',
'#between': 'between',
'#notBetween': 'notBetween',
'#in': 'in',
'#notIn': 'notIn',
'#like': 'like',
'#notLike': 'notLike',
'#iLike': 'iLike',
'#regexp': 'regexp',
'#iRegexp': 'iRegexp',
'#notIRegexp': 'notIRegexp',
'#overlap': 'overlap',
'#contains': 'contains',
'#contained': 'contained',
'#any': 'any',
'#col': 'col',
};
Кроме того, вышеуказанные шесть видов удобных сокращений можно комбинировать, но необходимо учитывать некоторые ограничения:
@
) может использоваться только для второго элемента массива;!
) не может использоваться для второго элемента массива;Op
), будет применяться только первое, а одинаковые обозначения операций в разных позициях будут перекрывать предыдущие.Мы можем создавать более мощные параметры интерфейса.
/*
以下接口参数逻辑, может помочь нам выполнить ☞
1. Запрос данных всех статей указанного пользователя.
2. Запрос данных статей указанного пользователя и указанных данных статьи.
3. Запрос данных статей указанного пользователя с приблизительным соответствием указанному заголовку статьи.
Обратите внимание, что id и title являются необязательными,
а uid должен быть указан.
*/
full: db.article
.where('uid')
.where('!id')
.where(['!title #like', d => `%${d.title}%`])
.findAll();
fuzzyQuery
, fuzzyQueryLeft
и fuzzyQueryRight
могут помочь вам быстро создать запрос с нечётким соответствием.
article
// принимает один параметр запроса
// по умолчанию это name
.fuzzyQuery('title')
.findAll()
article
// необязательно
.fuzzyQuery('!title')
.findAll()
pagination
помогает быстро создать логику разбиения на страницы.
article
// на каждой странице 10 записей данных
// count — количество записей на странице, по умолчанию 15
// page — номер страницы, начиная с которой нужно начать, по умолчанию 0
.pagination(10)
.findAll()
order
помогает легко создать сортировку.
article
// сортировка по дате создания в порядке убывания
.order(['createdAt', 'DESC'])
.findAll()
include
помогает добавить связь (дополнительные сведения о связывании см. в официальной документации sequelize)).
article
// запросить данные статьи, одновременно
// запрашивая данные пользователя и комментарии каждой статьи
.include(User, Comment)
.findAll()
remove
помогает удалить указанное поле из данных запроса.
set
позволяет изменить его.
article
// удалить поле status
// запретить пользователям обновлять его
.remove('status')
.update()
article
// изменить значение поля status на fall
set('status', 'fall')
.update()
it
похож на оператор if
, он может позволить логике вашего интерфейса иметь ветви, что очень важно для некоторых интерфейсов с небольшими различиями, вы можете объединить их в один интерфейс с помощью it
. Кроме того, он также может выполнять роль переключателя для определённых запросов.
it(condition, f1, [f2])
Его синтаксис:
it(условие, условие выполнено, условие не выполнено)
// поле
// когда comment == ture, выполнить f1, иначе f2
// обратите внимание, что внутреннее сравнение использует равенство
it('comment', f1, f2)
// функция
// когда count больше 2, выполнить f1, иначе f2
it(d => d.count > 2, f1, f2)
// другое, f1, f2. Может быть функцией или массивом функций
it('comment', f1, [f1, f2, f3])
// условие невыполнения может быть опущено
it('comment', [f1, f2, f3])
article
// когда комментарий,
// одновременно запрашивать данные комментариев каждой статьи
.it('comment', include(Comment))
not
является обратным вариантом it
.
not(условие, не выполнено, выполнено)
more
похож на switch
, но может разветвляться на несколько условий.
article
itField('sort', {
// когда sort = 'name', выполнить
'name': f1,
// когда sort = 'age', выполнить
'age': [f2, f3],
// когда sort = 'height', выполнить
'height': f4
})
.findAll()
Цепочка вызовов выглядит лаконично и элегантно, но ей не хватает хорошей возможности повторного использования. Когда у вас есть группа одинаковых или похожих логических интерфейсов, вы можете использовать независимые версии функций для повторного инкапсуляции, а затем вызвать метод scope
для добавления.
// импортировать независимый объект функций Scopes
const Scopes = Handle.Scopes
const {where, pagination, fuzzyQuery, include, order, it, merge} = Scopes
function nb () {
// использовать функцию слияния для объединения нескольких функций инструментов
return merge(
where('uid'),
where('!id'),
fuzzyQuery('!title'),
order(['createdAt', 'DESC']),
pagination(10),
)
}
article
.scope(nb)
.findAll()
Параметры объекта, объединённые методом scope
, действительны только для первого используемого метода. Если вы хотите, чтобы все текущие экземпляры модели использовали некоторые методы инструментов, вы можете добавить их через defaultScope
к экземпляру.
Вы также можете расширить пользовательские методы инструментов. Вам нужно добавить свои пользовательские функции инструментов в Handle.Scope
перед инициализацией.
Handle.Scope.myUtil = function (d) {
// вернуть полный объект параметров
return {
where: {
uid: d.uid
}
// другие параметры
}
}
Предоставляя функцию по умолчанию для указания параметров, вы можете глобально использовать пользовательскую функцию инструмента myUtil
.
article
.myUtil()
.findAll() **Является этапом для случаев, когда необходимо выполнить многотабличные операции над интерфейсом и дополнительно обработать возвращаемые данные.** Также это один из шагов при реализации более сложных интерфейсов.
```js
const find = artcile.process(async function (d) {
// 数据校验
// Опущено
// Запрос пользователя
const userData = await user
.where('username', 'password')
.rawFindOne()
// Запрос текущих статей пользователя
const result = await this
.where(['id', userData.id])
.rawFindAll()
// Возврат только рекомендованных статей
return result.filter(e => e.type === 'recommend')
})
По умолчанию метод process используется для запросов get, но Handle поддерживает 6 стандартных методов HTTP-запросов (get/head/put/delete/post/options).
articleStar.process('post', async function (d) {})
transaction — это простая оболочка для транзакций sequelize, встроенная в process. В использовании они полностью идентичны.
articleStar.transaction(async function (d) {
/** Обработка, связанная с транзакциями */
return /** Обработанные данные */
}),
Handle предоставляет три глобальных перехватчика before, after и data в объекте параметров. Каждый быстрый метод вызывает эти перехватчики, а метод процесса игнорирует их. Процесс вызывает before перед вызовом обратного вызова и after и data после него.
new Handle(model, {
// before-перехватчик выполняется перед операцией с базой данных
before (data, ctx, next) {
}
// after-перехватчик выполняется после операции с базой данных
after (result, ctx, next) {
}
// data-перехватчик может выполнять некоторую обработку до возврата данных на фронт или после перехвата исключений
data (err, result, ctx, next) {
}
})
Кроме того, каждый экземпляр метода имеет функции before и after, которые позволяют регистрировать перехватчики только для экземпляра. Это помогает нам выполнять некоторые полезные задачи.
Мы можем использовать before-перехватчик для проверки данных, отправленных фронтом.
article
.before(function (data) {
const {title} = data
if (!title) {
// Выбрасывается исключение
// Оно будет перехвачено глобальным data-перехватчиком
throw new Error('Заголовок статьи не может быть пустым')
}
if (title.length < 1 || title.code.length > 25) {
throw new Error('Длина заголовка статьи должна быть от 2 до 25 символов')
}
return data
})
.create()
Также можно использовать after-перехватчик для фильтрации данных.
article
.after(function (data) {
// Возвращается только количество статей
return data.length
})
.where('uid')
.findAll()
Обратите внимание, что before-перехватчик экземпляра выполняется раньше глобального before-перехватчика, а after-перехватчик экземпляра — позже глобального after-перехватчика.
Handle разумно генерирует параметры методов sequelize. Обычно нам не нужно об этом беспокоиться. Однако для increment, decrement или некоторых особых случаев вы можете использовать указанные данные вместо данных запроса, обработанных Handle. Для этого используйте метод raw.
// Увеличение поля hot
article
.raw('hot')
.increment('id')
Однако имейте в виду, что данные запроса всё равно будут использоваться в различных сценариях, таких как инструменты анализа области видимости и where. Просто данные запроса заменяются исходными данными при объединении в параметры метода sequelize. Поэтому изменение данных запроса в перехватчиках или других местах не влияет на доступ к базе данных. Благодаря этому вы можете использовать библиотеки, подобные mock, для пакетного добавления данных в базу данных. (Возможно, в будущем будет поддерживаться имитация данных с помощью mock.)
Если вы не можете найти функцию, которая решает вашу проблему в наборе инструментов, я настоятельно рекомендую вам обернуть соответствующий код в пользовательскую область видимости и использовать её. Во-первых, у вас будет элегантная структура кода и читаемые имена. Во-вторых, вам не придётся заново писать код при повторном использовании. Если ваша область видимости достаточно универсальна, вы можете отправить её в handle.js, чтобы помочь другим пользователям.
Если вы не используете pull requests или Issues, вы также можете связаться со мной следующим образом:
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )