Слой приложений Конструктор микрофронтов


Вопрос: Для каких задач необходим Конструктор микрофронтов?

Ответ: Конструктор микрофронтов – это low-code модуль для быстрой разработки пользовательского веб-интерфейса прикладных приложений. Он позволяет создавать специализированные интерфейсы без необходимости привлечения разработчиков. Позволяет настраивать поля, строки, столбцы и элементы управления (кнопки) индивидуально для каждого приложения.


Вопрос: На каком языке пишутся регулярные выражения для формул валидации и какие символы используются?

Ответ: Формулы валидации в системе пишутся на стандартном языке RegExp (Regular Expressions).

Для указания регулярного выражения необходимо использовать специальные символы ^, $, * , +, ?, {}, [], () и другие.

Основные правила:

  • ^ – начало строки;
  • $ – конец строки;
  • [abc] – один из символов a, b или c;
  • [0-9] – любая цифра (можно писать \d);
  • [A-Za-z] – любая латинская буква;
  • {n,m} – от n до m повторений (например, {3,5});
  • + – 1 или больше повторений;
  • – 0 или больше повторений;
  • ? – 0 или 1 повторение;
  • | – логическое «или» (например, jpg|png).

Примеры регулярных выражений:

  • Телефон: (+7XXXXXXXXXX): ^+7\d{10}$;
  • Email: ^[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,}$;
  • Пароль (8+ символов, буквы и цифры): ^(?=.[A-Za-z])(?=.\d).{8,}$.

Вопрос: На чем нужно акцентировать внимание при создании форм, конвейеров (пайплайнов) и коллекций в Конструкторе микрофронтов?

Ответ: Детали проектирования объектов и системных полей описаны в документации: Проектирование объектов и системные поля, доступные через #CONTEXT

При работе с пайплайнами для корректной работы шага «Показать форму сравнения файлов» нужно, чтобы у каждого из сравниваемых документов присутствовал «mimeTуре». Атрибут «mimeType» передается в контекст пайплайна внутри контрола FILE вместе с «fileld». Его нужно передавать при создании документа.


Вопрос: Для чего используется «Transformer» внутри конвектора данных при создании динамических приложений в Конструкторе микрофронтов? Для чего используется Handler?

Ответ: «Transformer» используется для настройки маппинга и преобразования значений, полученных из данных от бэкенда (формирующего источник данных в Конструкторе) и отображении для пользователя этих данных (например, в форме или коллекции создаваемого приложения).

Необходимо создать узлы «nodes», указав в каких ключах находится идентификатор, имя поля и тип узла, затем указать в «links» между какими узлами нужно выстроить связь.

Пример:

{ 
 "nodes": [ 
   { 
  "id":"i1", 
  "name":"preferredUsername", 
  "type":"DataInput" 
  }, 
  { 
  "id":"o1", 
  "name":"preferredUsername", 
  "type":"DataOutput" 
  }, 
], 
 "links": { 
 "i1":"o1", 
}, 
 "ui": { 
  "order": {} 
 } 
}

Handler используется для сложных преобразований данных при маппинге.

Пример:

{ 
  "nodes": [ 
   { 
   "id":"h5", 
   "name":"result_update", 
   "code":"''+#givenName+''+' '+''+#familyName+''", 
   "type":"DataHandler" 
   }, 
   { 
   "id":"o5", 
   "name":"result", 
   "type":"DataOutput" 
   } 
], 
  "links": { 
  "i5":"h5", 
  "h5":"o5" 
 }, 
  "ui": { 
    "order": {} 
  } 
}

Пример формирования динамического поля как значение двух системных полей, записанных через пробел:

{
 "nodes": [
   {
     "id":"i1",
     "name":"preferredUsername",
     "type":"DataInput"
     },
     {
     "id":"o1",
     "name":"preferredUsername",
     "type":"DataOutput"
     },
     {
     "id":"i2",
     "name":"givenName",
     "type":"DataInput"
     },
     {
     "id":"o2",
     "name":"givenName",
     "type":"DataOutput"
     },
     {
     "id":"i3",
     "name":"familyName",
     "type":"DataInput"
     },
     {
     "id":"o3",
     "name":"familyName",
     "type":"DataOutput"
     },
     {
     "id":"i4",
     "name":"email",
     "type":"DataInput"
     },
     {
     "id":"o4",
     "name":"email",
     "type":"DataOutput"
     },
     {
     "id":"i5",
     "name":"result",
     "type":"DataInput"
     },
     {
     "id":"h5",
     "name":"result_update",
     "code":"''+#givenName+''+' '+''+#familyName+''+' '+'('+#preferredUsername+')'",
     "type":"DataHandler"
     },
     {
     "id":"o5",
     "name":"result",
     "type":"DataOutput"
     },
     {
     "id":"i6",
     "name":"creationDate",
     "type":"DataInput"
     },
     {
     "id":"o6",
     "name":"creationDate",
     "type":"DataOutput"
     }
],
 "links": {
 "i1":["o1","h5"],
 "i2":["o2","h5"],
 "i3":["o3","h5"],
 "i4":"o4",
 "i5":"h5",
 "h5":"o5",
 "i6":"o6"
 },
 "ui": {
  "order": {}
 }
}

Пример трансформера, который извлекает различные данные из переменной, в которой хранится объект (поступает из источника данных с типом PIPELINE):

{
 "nodes": [
  { "id": "i1", "name": "id", "type": "DataInput" },
  { "id": "o1", "name": "id", "type": "DataOutput" },

  { "id": "i2", "name": "versionId", "type": "DataInput" },
  { "id": "o2", "name": "versionId", "type": "DataOutput" },

  { "id": "i3", "name": "documentMain", "type": "DataInput" },
  { "id": "o3", "name": "documentMain", "type": "DataOutput" },

  {
   "id": "h4",
   "name": "name_extracted",
   "code": "#documentMain['name']",
   "type": "DataHandler"
  },
  { "id": "o4", "name": "name", "type": "DataOutput" },

  {
   "id": "h5",
   "name": "className_extracted",
   "code": "#documentMain['className']",
   "type": "DataHandler"
  },
  { "id": "o5", "name": "className", "type": "DataOutput" },

  {
   "id": "h6",
   "name": "creationDate_extracted",
   "code": "#documentMain['creationDate']",
   "type": "DataHandler"
  },
  { "id": "o6", "name": "creationDate", "type": "DataOutput" },

  {
   "id": "h7",
   "name": "modificationDate_extracted",
   "code": "#documentMain['modificationDate']",
   "type": "DataHandler"
  },
  { "id": "o7", "name": "modificationDate", "type": "DataOutput" },

  {
   "id": "h8",
   "name": "recordVersion_extracted",
   "code": "#documentMain['recordVersion']",
   "type": "DataHandler"
  },
  { "id": "o8", "name": "recordVersion", "type": "DataOutput" },

  { "id": "i9", "name": "documentsIncludedFileIds", "type": "DataInput" },
  { "id": "o9", "name": "documentsIncludedFileIds", "type": "DataOutput" }
 ],
 "links": {
  "i1": "o1",
  "i2": "o2",
  "i3": ["o3", "h4", "h5", "h6", "h7", "h8"],
  "h4": "o4",
  "h5": "o5",
  "h6": "o6",
  "h7": "o7",
  "h8": "o8",
  "i9": "o9"
 },
 "ui": { "order": {} }
}

Вопрос: К каким системным полям можно обращаться через контекст в конвейере (пайплайне), в формулах видимости, блокировки или обязательности?

Ответ: Данные поля описаны в документации: Проектирование объектов и системные поля, доступные через #CONTEXT


Вопрос: Почему не удается выбрать класс объекта при создании источника данных в Конструкторе микрофронтов для нового приложения? При этом классы созданы и прописаны в конфигурации.

Ответ: Необходимо заполнить прокси Конструктора микрофронтов. Сервис «dh-facade-service» обращается к другим сервисам и получает список классов по прокси. Если при проксировании не был прописан правильный путь, то список классов не будет получен источником данных.


Вопрос: В динамическом приложении возникает ошибка «Не найден класс» (404) при выполнении запроса. Каковы возможные способы решения?

Ответ: Для диагностики и устранения проблемы рекомендуется:

  1. Проверить источник данных.
  2. Проверить настройку прокси.
  3. Проверить существование класса в конфигурациях.
  4. Проверить корректность запроса, воспользовавшись функциональной кнопкой «Send» в интерфейсе.
  5. Повторно сохранить настройку в сервисе конфигураций, в котором находится данный класс.

Вопрос: Почему в созданном динамическом приложении отсутствуют стандартные кнопки из статических сервисов?

Ответ: Это ожидаемое поведение. Весь функционал, включая кнопки, элементы управления и таблицы, в динамических приложениях создается вручную.

Для добавления кнопки необходимо:

  1. Перейти в конструктор созданного приложения.
  2. Создать новое действие.
  3. Выбрать иконку для кнопки.
  4. Назначить действие, которое будет выполняться при нажатии кнопки (например, вызов API, навигация).
  5. После создания элемента публикуется разрешение (пермиссия). Необходимо в сервисе прав доступа выдать данное разрешение на конкретную роль. После выполнения этих настроек кнопка появится в интерфейсе приложения.

Вопрос: Что такое FreeMarker и для чего он используется?

Ответ: FreeMarker – это механизм шаблонов, позволяющий редактировать полученные JSON-ответы. В cистеме FreeMarker используется для сборки результатов нужной структуры и формата по данным контекста, позволяя менять значения, добавлять или удалять поля и возвращать готовый результат в заданном типе.

В Системе для FreeMarker предусмотрены две дополнительные настройки:

1. Тип выводимого значения (STRING / INT / FLOAT / BOOLEAN / OBJECT) – указывает бэкенду, как интерпретировать полученный текст:

  • STRING – сохранить как строку;
  • INT – преобразовать в целое число (если возможно);
  • FLOAT – преобразовать в число с плавающей точкой (если возможно);
  • BOOLEAN – преобразовать в логическое значение (если возможно);
  • OBJECT – преобразовать текст как JSON и вернуть объект/массив (если возможно).

2. Преобразование пустых строк (доступна только опция BLANK_TO_NULL) – если итоговый вывод пустой, он заменяется на значение «null».

Примеры использования Freemarker описаны в документации для обновления контекста: Обновление контекста

Обновление контекста, а также массовое редактирование описано в документации: Массовое редактирование

Примеры использования:

Необходимо привести преобразование сертификата цифровой подписи в определенный формат. Из информации, полученной о сертификате в формате JSON, нужно объединить переменные из значения «value»:

"issuerName": [
  {
   "isRequired": false,
   "order": 0,
   "oid": "2.5.4.3",
   "name": null,
   "value": "Тестовый УЦ для стенда DSS 2012",
   "stringIdentifier": "CN"
  },
  {
   "isRequired": false,
   "order": 0,
   "oid": "2.5.4.10",
   "name": null,
   "value": "ООО \"КРИПТО-ПРО\"",
   "stringIdentifier": "O"
  }, ....

Структуру информации, уже созданной ЭП, необходимо очистить от кавычек и таких конструкций как «CN=», «O=»:

"issuerName": "CN=Тестовый УЦ для стенда DSS 2012, O=\"ООО \"\"КРИПТО-ПРО\"\"\", L=Москва, C=RU, INN=007717107991, OGRN=1037700085444, E=info@cryptopro.ru",

Для этого можно воспользоваться таким преобразованием:

<#assign issuerName = CONTEXT.issuerName>
<#-- Разбиение на части по запятым и обработка каждой части -->
<#assign parts = issuerName?split(",")>
<#assign resultParts = []>
<#list parts as part>
  <#assign cleanedPart = part?trim>
  <#if cleanedPart?starts_with("CN=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("CN=", "")]>
  <#elseif cleanedPart?starts_with("O=")>
    <#assign orgPart = cleanedPart?replace("O=", "")?replace('\"', '')?replace('\"\"', '"')>
    <#assign resultParts = resultParts + [orgPart]>
  <#elseif cleanedPart?starts_with("L=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("L=", "")]>
  <#elseif cleanedPart?starts_with("C=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("C=", "")]>
  <#elseif cleanedPart?starts_with("INN=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("INN=", "")]>
  <#elseif cleanedPart?starts_with("OGRN=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("OGRN=", "")]>
  <#elseif cleanedPart?starts_with("E=")>
    <#assign resultParts = resultParts + [cleanedPart?replace("E=", "")]>
  </#if>
</#list>
<#-- Сборка в общую строку -->
${resultParts?join(", ")}