Урок 25. Создание API-запросов

В этом уроке мы разберем, как создавать кастомные API-запросы, которые позволят сторонним сервисам взаимодействовать с нашим сервером.

Для тестирования API-запросов в уроке используется приложение Postman, которое можно скачать с официального сайта.

Создание пользователя

Все кастомные API-запросы, которые отправляются на сервер из мобильных приложений или с сайта, должны быть подписаны токеном, который создается при авторизации пользователя в программе.

Для начала добавим специального пользователя "Пользователь для сайта", под которым будет авторизовываться сторонний ресурс для доступа к API:

DO
$BODY$
DECLARE
  _permission_block_id smallint;
  _permission_block_item_id smallint;
  _permission_id smallint;

  _group_id smallint;
  _public_user_id smallint;
  _user_id smallint;
  
BEGIN

  -- Добавление разрешений
  INSERT INTO template.permission_block(id_title, title)
  VALUES ('site', 'Сайт')
  ON CONFLICT (id_title) DO NOTHING
  RETURNING permission_block_id INTO _permission_block_id;

  INSERT INTO template.permission_block_item(permission_block_id, id_title, title)
  VALUES (_permission_block_id, 'site_api', 'API-запросы для сайта')
  ON CONFLICT (id_title) DO NOTHING
  RETURNING permission_block_item_id INTO _permission_block_item_id;

  INSERT INTO template.permission(name, permission_block_item_id)
  VALUES ('SiteApiMethodPermission', _permission_block_item_id)
  ON CONFLICT (name) DO NOTHING
  RETURNING permission_id INTO _permission_id;

  INSERT INTO public.strings (strings_id, language_id, text_value)
  VALUES
    ('site', 1, 'Сайт'),
    ('site', 2, 'Site'),
    ('site_api', 1, 'API-запросы для сайта'),
    ('site_api', 2, 'Website API requests');
  -- Добавление разрешений

  -- Создание группы
  INSERT INTO template.group (name, title, description)
  VALUES ('UserForSiteGroup', 'Пользователи для сайта', 'Группа пользователей для работы с сайтом')
  RETURNING group_id INTO _group_id;
  
  INSERT INTO template.group_permission(group_id, permission_id)
  VALUES (_group_id, _permission_id)
  ON CONFLICT (group_id, permission_id) DO NOTHING;
  -- Создание группы

  -- Создание пользователя
  CREATE EXTENSION pgcrypto;
  
  INSERT INTO public.user(user_name, user_full_name, user_password, person)
  VALUES('USER_FOR_SITE', 'Пользователь для сайта', encode(digest('123', 'sha512'), 'hex'), false)
  RETURNING user_id INTO _public_user_id;

  INSERT INTO template.user(public_user_id)
  VALUES(_public_user_id)
  RETURNING user_id INTO _user_id;

  INSERT INTO template.user_group(user_id, group_id)
  VALUES(_user_id, _group_id);

  DROP EXTENSION pgcrypto;
  -- Создание пользователя

END;
$BODY$;

Для пользователя создали группу, которой сразу назначили права доступа до SiteApiMethodPermission. Позже создадим это разрешение в серверном xml-файле.

Из запроса на получение списка групп пользователей необходимо исключить группу "Пользователи для сайта", чтобы нельзя было редактировать ее и выбирать при создании/редактировании пользователей.

Необходимо исключить блок "Сайт" из списка редактирования прав доступа для групп пользователей (PermissionBlockItemSelectSqlQuery).

Аутентификация

Запрос аутентификации

Для авторизации и получения токена используется POST-запрос /api/v1/tokens/signin.

Давайте создадим такой запрос в Postman:

Где localhost:49707 - IP-адрес и порт сервера.

Тело запроса:

В теле запроса указаны три поля:

  • UserName - логин пользователя. Обязательное поле. Ожидается строковое значение;

  • PasswordHash - хэш пароля (Sha512). Обязательное поле. Ожидается строковое значение;

  • LongToken - признак использования токена с большим временем жизни. Необязательное поле. Ожидается логическое значение. По умолчанию используется значение False.

Если LongToken имеет значение false, то время жизни токена равно 10 минутам. Если true, то 15 годам.

В ответ от сервера мы получим:

В ответе поля:

  • accessToken - основной токен доступа. Им необходимо подписывать все запросы;

  • refreshToken - токен, необходимый для обновления основного токена;

  • expires - срок жизни (точнее, срок годности) accessToken, указанный в секундах с 1970-01-01.

Запрос на обновление токена

Для обновления токена используется POST-запрос /api/v1/tokens/refresh:

Тело запроса:

В поле Token указывается refreshToken, полученный в POST-запросе /api/v1/tokens/signin.

Ответ от сервера буде содержать те же поля, что и в запросе на аутентификацию:

Отлично, теперь можем приступить к созданию кастомных API-запросов.

API-запросы

Перейдем в серверный xml-файл (Template.xml) и перед тэгом <Scheduler> добавим тэг <ApiMethods>, в котором будем описывать кастомные запросы.

Шаблон маршрута для кастомных API-запросов имеет вид:

http://localhost:49707/data_api/{route}

где {route} - конечная точка маршрута.

GET-запрос

Создадим API-запрос на получение списка единиц измерения:

В атрибуте Route тэга <ApiMethod> указываем конечную точку маршрута, по которому будет выполняться действие, описанное в ApiMethod. В нашем случае полный маршрут будет иметь вид: http://localhost:50707/data_api/unit_list.

Необязательный тэг <Response> описывает дополнительные поля в ответе на запрос. Таким образом, помимо системного поля result_code, будет добавлено поле units, содержащее массив объектов. За то, что поле будет возвращать массив, отвечает необязательный атрибут Array тэга <Object>. В качестве значения поля units будет подставляться результат выполнения команды UnitListSelectSqlQueryCommand типа SqlQueryCommand:

Добавим API-метод GetUnitListApiMethod в разрешение SiteApiMethodPermission, которое ранее добавили для группы "Пользователи для сайта":

Давайте перейдем в Postman и создадим запрос localhost:49707/data_api/unit_list для проверки метода:

Все запросы должны быть подписаны полученным при аутентификации jwt-токеном, поэтому в заголовок запроса добавили параметр Authorization со значением: Bearer <accessToken>.

В ответ на запрос сервер вернет json-объект:

GET-запрос с двумя объектами

Создадим команды для получения списков категорий ТМЦ и самих ТМЦ:

Добавим API-метод для получения этих списков:

В Postman создадим и выполним запрос localhost:49707/data_api/material_list:

В ответ на запрос сервер вернет json-объект:

В ответе мы видим два поля: material_category и material с массивами объектов.

GET-запрос с параметрами

Давайте создадим API-запрос, который по идентификатору клиента будет возвращать список его заказов.

Создадим команду:

В тексте запроса используется переменная client_id. Вместо этой переменной будет подставляться значение параметра, полученного в HTTP-запросе.

Добавим ApiMethod:

В тэге <Parameters> можем перечислять все входные параметры. Для каждого параметра необходимо указать его тип и источник. В нашем случае для GET-запроса будем ожидать параметр client_id в строке запроса. Через атрибут Required укажем, что параметр является обязательным.

Выполним в Postman запрос localhost:49707/data_api/order_list?client_id=2:

В ответ на запрос сервер вернет json-объект:

Так как параметру client_id указали источник FromQuery (значение атрибута Source), то контроллер будет искать этот параметр в строке запроса.

Давайте расширим этот запрос, добавив в него список позиций заказа:

Мы можем просто добавить список позиций заказа отдельным полем:

А можем, используя тэг <Relations>, описать отношения полей друг другу:

В тэге <Relation> указываем имена родительского и дочернего объектов и их поля, по которым будет строиться дерево отношений.

Выполним тестовый запрос localhost:49707/data_api/order_list?client_id=2:

В ответ на запрос сервер вернет json-объект:

В ответе от сервера каждый объект заказа имеет массив позиций заказа.

Можно указывать несколько отношений. Добавьте команду на получение списка оплат в заказах для клиента и добавьте их в OrderListByClientIdApiMethod. Укажите в тэге <Relation> отношение списка оплат к списку заказов.

POST-запрос

В качестве примера POST-запроса создадим API-метод на добавление оплаты в заказ.

Для начала добавим в таблицу template.account колонку for_site для обозначения счета, на который будут привязываться оплаты с сайта, и добавим такой счет:

Создадим функцию для добавления оплаты через API-метод:

Функция будет возвращать идентификатор добавленной записи, либо ошибку, если передан неверный идентификатор заказа, заказ удален или отсутствует счет для оплат с сайта.

Создадим команду для вызова функции:

Функция template.order_payment_insert() принимает два параметра, которые должны быть обязательными для запроса на добавления оплаты в заказ.

Создадим ApiMethod, который будет выполнять команду:

В тэге <Commands> можем описать последовательность команд, а в тэге <Response> обращаться к результату выполнения команд. При этом мы можем через атрибут Parameter обратиться к конкретному значению в результате команды.

Выполним тестовый запрос localhost:49707/data_api/add_order_payment:

В ответ на запрос сервер вернет json-объект:

Кастомные типы параметров

Давайте создадим API-метод на сохранение заказа одним запросом. Т.е. помимо основной информации о заказе будем принимать и сохранять позиции заказа. У нас будет один метод на создание нового заказа и на редактирование существующего.

Первым делом в Postman создадим запрос localhost:49707/data_api/save_order:

Запрос будет передавать json-объект вида:

Для источника FromBody есть ограничение на обработку параметров относительно уровня вложенности объектов. В качестве параметров запроса можем указывать только поля основного объекта, которые принимают значение простых типов (строка, число, дата, логическое) и массив простых типов. Мы можем напрямую обратиться к полю client_id и получить его значение, но не можем обратиться к полям объекта order. Поэтому в ApiMethod мы укажем параметр order с типом Json:

Следовательно, в команде SaveOrderSqlQueryCommand с параметром order будем работать как с json-строкой:

Подробно почитать о функциях и операторах, поддерживаемых PostgreSQL для работы с JSON, можете по ссылке.

Функция сохранения заказа будет иметь вид:

Выполним тестовый запрос:

POST-запрос с файлами

Чтобы в кастомные API-запросы передавать файлы, HTTP-запрос должен использовать тип содержимого multipart/form-data.

Перейдем в Postman и создадим запрос localhost:49707/data_api/save_client:

Запрос содержит два объекта формы:

  • client - json-объект с описанием данных клиента;

  • file - передаваемый файл.

Объект client будет иметь вид:

Создадим ApiMethod для обработки такого запроса:

В качестве источника данных укажем FormData.

Если мы хотим передать на сервер массив файлов, то достаточно добавить в форму запроса еще один объект с тем же именем:

А в ApiMethod для параметра file добавить атрибут Array со значением True. В противном случае в запрос будет подставляться информация о последнем файле.

Команда SaveClientSqlQueryCommand будет иметь вид:

Получив соответствующий запрос, сервер загрузит файл, сгенерирует guid и добавит информацию о нем в таблицу public.file. Для переменной file будет сгенерирована json-строка в виде массива объектов с двумя полями: file_name (имя файла) и guid (сгенерированный идентификатор).

Таким образом, текст запроса с замененными переменными будет иметь вид:

Создайте самостоятельно функцию template.save_client(json, json), которая будет создавать нового клиента или обновлять данные существующего. В качестве результата функция должна возвращать идентификатор клиента.

В ответ на запрос сервер вернет json-объект вида:

Работа с файлами

Загрузка файла

Для загрузки файла на сервер используется системный POST-запрос /files/upload, который в форме запроса должен иметь объект с именем file. После сохранения файла на сервере в ответе в поле FileGuid вернется сгенерированный guid.

Скачивание файла

Для скачивания файлов с сервера используется системный GET-запрос /files/download, ожидающий обязательный параметр fileGuid - guid файла, который генерируется автоматически при загрузке файла на сервер.

Блокировка API-запросов

Скорректируйте форму настроек, добавив объект CheckBox для включения/отключения модуля для работы с сайтом и кнопку для смены пароля у пользователя для сайта.

По кнопке "Изменить пароль..." должна открываться форма смены пароля (TemplateUserPasswordEdit.xml), где для пользователя "Пользователь для сайта" (user_name = 'USER_FOR_SITE') можно задать новый пароль. Измените самостоятельно логику смены пароля.

В таблице template.settings добавим колонку для включения/отключения модуля сайта:

Теперь в файле Template.xml можем создать условие для проверки активности модуля сайта:

Добавим в GetUnitListApiMethod необязательный тэг <Enabled>, в котором укажем новое условие:

Сделайте то же самое для остальных методов.

Если API-метод недоступен, то в ответ на запрос будет возвращаться код 404 Not Found:

Итоги

В этом уроке мы познакомились с возможностью создавать кастомные API-запросы для предоставления доступа сторонним сервисам, таким как сайты или мобильные приложения.

Помимо кастомных API-запросов рассмотрели штатные запросы для аутентификации пользователей и для работы с файлами.

Как маленький бонус: узнали о возможностях PostgreSQL по работе с JSON - в следующем уроке рассмотрим, как можно на форме создавать json-объекты для отправки их на сервер.

Ответы

В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.

605KB
Open

Ниже прикреплен экспортированный из Postman файл с коллекцией запросов:

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

Last updated