WebServicesDeveloperGuide: различия между версиями
Vromav (обсуждение | вклад) |
м |
||
Строка 677: | Строка 677: | ||
Ответ - True | Ответ - True | ||
Выполнено | Выполнено | ||
[[Категория:Для разработчика]] |
Текущая версия на 09:10, 21 апреля 2023
Руководство по работе с веб-сервисами сервера
Версия 4.6.0
© 2015-2023, ООО "Процессные технологии"
# Описание веб–сервисов сервера
Запуск веб-сервисов происходит автоматически при запуске RunaWFE–сервера. Веб-сервисы физически находятся в wfe-service-4.X.X.jar.
В RunaWFE версии 4 API веб-сервисов и Java API унифицированы за исключением нескольких методов с сигнатурами, содержащими сложные структуры и карты (java.util.Map) из-за ограничений веб-сервисов.
Во всем документе для формирования конкретного адреса нужно заменить 4.X.X на реальный номер версии системы.
# Определение версии сервера
Для использования в УРЛ-ах сервисов требуется указывать версию сервера. Её можно получать по адресу http://host:port/wfe/version (аутентификация не требуется).
# Доступ к веб-сервисам
# WildFly10 и Jboss7
http://localhost:8080/wfe-service-4.X.X/НазваниеВебСервиса/НазваниеИнтерфейса?wsdl
Название и описание сервиса | Локальный WSDL | Ссылка на описание методов (операций) |
AuthenticationServiceBean
Сервис аутентификации пользователей |
WildFly и Jboss7 | Открыть файл
|
AuthorizationServiceBean
Сервис авторизации операций (проверка полномочий) |
WildFly и Jboss7 | Открыть файл
|
BotServiceBean
Сервис по управлению бот станциями, ботами и заданиями ботов |
WildFly и Jboss7 | Открыть файл
|
DefinitionServiceBean
Сервис управления определениями процессов |
WildFly и Jboss7 | Открыть файл
|
ExecutionServiceBean
Сервис управления процессами и контроля их исполнения |
WildFly и Jboss7 | Открыть файл
|
TaskServiceBean
Сервис по работе с заданиями |
WildFly и Jboss7 | Открыть файл
|
ExecutorServiceBean
Сервис по работе с исполнителями (пользователями и группами) |
WildFly и Jboss7 | Открыть файл
|
ProfileServiceBean
Сервис по работе с профилями пользователей |
WildFly и Jboss7 | Открыть файл
|
RelationServiceBean
Сервис по работе с отношениями |
WildFly и Jboss7 | Открыть файл
|
SystemServiceBean
Сервис содержит общие операции по работе с системой |
WildFly и Jboss7 | Открыть файл
|
ScriptingServiceBean
Сервис для удаленного выполнения скриптов на стороне сервера |
WildFly и Jboss7 | Открыть файл
|
SubstitutionServiceBean
Сервис по работе с правилами замещения и их критериями |
WildFly и Jboss7 | Открыть файл
|
BotInvokerServiceBean 4.2.0+
Сервис по управлению бот станцией (запуск, остановка, получение статуса) |
WildFly и Jboss7 | Открыть файл
|
ReportServiceBean
Сервис по работе с отчетами |
WildFly и Jboss7 | Открыть файл
|
DataSourceServiceBean
Сервис по работе с источниками данных |
WildFly и Jboss7 | Открыть файл
|
ChatServiceBean
Сервис по работе с чатом участников экземпляра бизнес-процесса |
WildFly и Jboss7 | Открыть файл
|
AuditServiceBean
Сервис по работе с логами системы и выполнения бизнес-процессов |
WildFly и Jboss7 | Открыть файл
|
# Работа с переменными 4.1.0+
В связи с реализацией сложных переменных работа с переменными была изменена (в части простых переменных тоже).
При операциях получения переменных заполнены следующие атрибуты:
Название | Значение |
name | название переменной |
scriptingName | название переменной для использования в скриптах |
format | формат определяет тип переменной
для простых переменных принимает одно из значений: (string, text, integer, double, bigdecimal, boolean, date, datetime, time, file) типы для работы с исполнителями характеризуются значениями: (executor, user, group) компонентные типы характеризуются значениями: (list(?), map(?, ?)) с указанием типа компонент вместо вопросов для пользовательских типов формируется описание на основании атрибутов в формате JSON |
value | значение переменной в формате JSON |
При операциях отправки переменных нужно заполнять только атрибуты name и value, остальные атрибуты будут взяты из определения процесса.
Значение типа file представляется в виде, где для представления массива байт data используется кодировка Base64:
{ "fileName": "test.txt", "contentType": "text/plain", "data": "IyEvdXNyL2Jpbi9lbnYgcHl0aG9uCgo=" }
Значение типа (executor, user, group) представляется в виде:
{ "id": 44, "name": "kermit", "fullName": "Kermit Alex" }
При отправке переменной заполнить можно только атрибут id или name.
# Примеры
# Вызов веб-сервисов из клиента на языке Java
Проект wfe-webservice-client создан для авто-генерации клиентской библиотеки на основе серверных сервисов. В нём же есть примеры использования.
Сборку проекта можно осуществить с помощью команды в проекте wfe-webservice-client.
mvn clean install -Dappserver=jboss7 -Dmaven.test.skip=true
Перед сборкой необходимо запустить сервер (используется утилита wsconsume).
Другим вариантом генерации классов, требуемых для взаимодействия с веб-сервисом из WSDL является Jboss-утилита wsconsume.bat(sh), которой можно воспользоваться. Также можно скачать уже сгенерированный набор классов с исходниками runawfe-ws-client.4.X.X.jar. После этого необходимо добавить классы в classpath и использовать их API.
# Получения списка заданий пользователя
Пример:
public static void main(String[] args) {
try {
AuthenticationAPI authenticationAPI = new AuthenticationWebService().getAuthenticationAPIPort();
User user = authenticationAPI.authenticateByLoginPassword("Administrator", "wf");
TaskAPI taskAPI = new TaskWebService().getTaskAPIPort();
List<WfTask> tasks = taskAPI.getTasks(user, null);
System.out.println("TASKS = " + tasks.size());
for (WfTask task : tasks) {
System.out.println(" Task " + task.getName() + " assigned to " + task.getOwner().getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
# Работа с сигналами
В RunaWFE 4.4.1 были добавлены команды для работы с сигналами.
boolean signalReceiverIsActiveWS(User user, Map<String, String> routingData) - проверка, может ли сигнал быть обработан немедленно (существует ли по крайней мере один соответствующий активный получатель CatchEventNode)
void sendSignalWS(User user, Map<String, String> routingData, Map<String, ?> payloadData, long ttlInSeconds) - отправка сигнала получателям (CatchEventNode)
Тестовый процесс с узлом ожидания сигнала, в свойствах маршрутизации processDefinitionName=${currentDefinitionName}.
Файл:Sample1655.par
Код проверяющий наличие активного получателя со свойствами маршрутизации "processDefinitionName"="sample1655-unknown" и "processDefinitionName"="sample1655". А также непосредственно отправка сигнала в ожидающий в БП "sample1655" узел.
public static void main(String[] args) {
try {
AuthenticationAPI authenticationAPI = new AuthenticationWebService().getAuthenticationAPIPort();
User user = authenticationAPI.authenticateByLoginPassword("Administrator", "wf");
ExecutionAPI executionAPI = new ExecutionWebService().getExecutionAPIPort();
{
List<StringKeyValue> routingData = new ArrayList<>();
routingData.add(create("processDefinitionName", "sample1655-unknown"));
System.out.println(executionAPI.signalReceiverIsActiveWS(user, routingData));
}
{
List<StringKeyValue> routingData = new ArrayList<>();
routingData.add(create("processDefinitionName", "sample1655"));
System.out.println(executionAPI.signalReceiverIsActiveWS(user, routingData));
}
{
List<StringKeyValue> routingData = new ArrayList<>();
routingData.add(create("processDefinitionName", "sample1655"));
List<StringKeyValue> payloadData = new ArrayList<>();
payloadData.add(create("stringValue", "sample"));
payloadData.add(create("datetimeValue", "17.02.2020 15:17:44"));
executionAPI.sendSignalWS(user, routingData, payloadData, 1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static StringKeyValue create(String key, String value) {
StringKeyValue kv = new StringKeyValue();
kv.setKey(key);
kv.setValue(value);
return kv;
}
# Вызов веб-сервисов из клиента на языке Python
Используя библиотеку suds можно взаимодействовать с сервером посредством веб-сервисов.
Запуск процесса
from suds.client import Client
authentication = Client("http://localhost:8080/wfe-service-4.X.X/AuthenticationWebService/AuthenticationAPI?wsdl")
user = authentication.service.authenticateByLoginPassword("Administrator", "wf")
execution = Client("http://localhost:8080/wfe-service-4.X.X/ExecutionWebService/ExecutionAPI?wsdl")
variable1 = execution.factory.create('{http://impl.service.wfe.runa.ru/}variable')
variable1.name="string"
variable1.value="Test"
variable2 = execution.factory.create('{http://impl.service.wfe.runa.ru/}variable')
variable2.name="date"
variable2.value="01.01.2012 00:00"
variable3 = execution.factory.create('{http://impl.service.wfe.runa.ru/}variable')
variable3.name="number"
variable3.value=2012
variable4 = execution.factory.create('{http://impl.service.wfe.runa.ru/}variable')
variable4.name="executor"
variable4.value="{\"id\":1}"
print(execution.service.startProcessWS(user, "demo variables test", [variable1, variable2, variable3, variable4]))
Получение формы узла определения БП (по идентификаторам определения БП и узла)
from suds.client import Client
import base64
authentication = Client("http://localhost:8080/wfe-service-4.X.X/AuthenticationWebService/AuthenticationAPI?wsdl")
user = authentication.service.authenticateByLoginPassword("Administrator", "wf")
definition = Client("http://localhost:8080/wfe-service-4.X.X/DefinitionWebService/DefinitionAPI?wsdl")
execution = Client("http://localhost:8080/wfe-service-4.X.X/ExecutionWebService/ExecutionAPI?wsdl")
processDefinition = definition.service.getLatestProcessDefinition(user, "demo variables test")
interaction = definition.service.getTaskNodeInteraction(user, processDefinition.id, "ID1")
print(base64.b64decode(interaction.formData).decode("utf-8"))
Получение списка определений переменных в определении процесса
from suds.client import Client
import base64
authentication = Client("http://localhost:8080/wfe-service-4.X.X/AuthenticationWebService/AuthenticationAPI?wsdl")
user = authentication.service.authenticateByLoginPassword("Administrator", "wf");
definition = Client("http://localhost:8080/wfe-service-4.X.X/DefinitionWebService/DefinitionAPI?wsdl")
processDefinition = definition.service.getLatestProcessDefinition(user, "demo variables test")
variables = definition.service.getVariableDefinitionsWS(user, processDefinition.id)
print(variables)
Получение переменных процесса
from suds.client import Client
import base64
authentication = Client("http://localhost:8080/wfe-service-4.X.X/AuthenticationWebService/AuthenticationAPI?wsdl")
user = authentication.service.authenticateByLoginPassword("Administrator", "wf")
execution = Client("http://localhost:8080/wfe-service-4.X.X/ExecutionWebService/ExecutionAPI?wsdl")
print(execution.service.getVariablesWS(user, 46))
Обновление переменных процесса
from suds.client import Client
import base64
authentication = Client("http://localhost:8080/wfe-service-4.X.X/AuthenticationWebService/AuthenticationAPI?wsdl")
user = authentication.service.authenticateByLoginPassword("Administrator", "wf")
execution = Client("http://localhost:8080/wfe-service-4.X.X/ExecutionWebService/ExecutionAPI?wsdl")
variable = execution.factory.create('{http://impl.service.wfe.runa.ru/}variable')
variable.name = "editors"
variable.value = "[\"Scaners\", \"Ports\", \"Enumerators\"]"
print(execution.service.updateVariablesWS(user, 46, variable))
# Загрузка определения процесса на сервер
import argparse
import urllib.request
from zeep import Client
LOGIN = "Administrator"
PWD = "wf"
DEFINITION_NAME = "complete_task_sample"
wfe_version = urllib.request.urlopen("http://localhost:8080/wfe/version").read().decode('utf-8')
if wfe_version:
BASE_URL = "http://localhost:8080/wfe-service-" + wfe_version
AUTHENTICATION_API_URL = BASE_URL + "/AuthenticationWebService/AuthenticationAPI?wsdl"
DEFINITION_API_URL = BASE_URL + "/DefinitionWebService/DefinitionAPI?wsdl"
parser = argparse.ArgumentParser()
parser.add_argument('-par')
args = parser.parse_args()
if args.par is not None:
print("Authentication:")
authentication = Client(AUTHENTICATION_API_URL)
user = authentication.service.authenticateByLoginPassword(LOGIN, PWD)
print(" OK");
definition_api = Client(DEFINITION_API_URL)
with open(args.par, "rb") as par_file:
if args.par.endswith('.par'):
definitionName = args.par[:-4]
bytes = par_file.read()
par_file.close()
id = None
try:
definitionResponse = definition_api.service.getLatestProcessDefinition(user, definitionName)
id = definitionResponse.id
except BaseException as err:
pass
if id is None:
print("Deploy", definitionName, ":")
definition_api.service.deployProcessDefinition(user, bytes, ["GPD"])
print(" OK")
else:
print("Redeploy definition", definitionName, ":")
definition_api.service.redeployProcessDefinition(user, id, bytes, ["GPD"])
print(" OK")
Пример запуска
D:\>deploy_par.py -par "complete task sample.par" Authentication: OK Deploy definition complete task sample : OK
# Пример использования BatchPresentation 4.4.3+
Перед выполнением:
- определите версию сервера (см. пункт WebServicesDeveloperGuide#DetermineServerVersion данного руководства)
- загрузите БП "long-process.par" Файл:Long-process.par и запустите несколько экземпляров данного процесса
- создайте на сервере отношение "New relation"
from suds.client import Client
def print_var_names(variables):
result = ""
try:
for var in variables:
result += " | " + var.definition.name + " : " + str(var.value)
except Exception:
result += " | " + var.definition.name + " : " + "null"
return result
def create_batch_presentation(presentation_type, page_number, page_size, variables, filters, sortings):
request = task_api.factory.create('wfBatchPresentation')
request.classPresentationType = presentation_type
request.pageNumber = page_number
request.pageSize = page_size
request.variables = variables
request.filters = filters
request.sortings = sortings
return request
def create_filter(name, value, exclusive):
filter = execution_api.factory.create("filter")
filter.name = name
filter.value = value
filter.exclusive = exclusive
return filter
def create_sorting(name, order):
sorting = execution_api.factory.create("sorting")
sorting.name = name
sorting.order = order
return sorting
def create_executor(id):
executor = executor_api.factory.create("wfExecutor")
executor.id = id
executor.name = ""
return executor
AUTHENTICATION_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/AuthenticationWebService/AuthenticationAPI?wsdl"
EXECUTION_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/ExecutionWebService/ExecutionAPI?wsdl"
AUDIT_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/AuditWebService/AuditAPI?wsdl"
DEFINITION_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/DefinitionWebService/DefinitionAPI?wsdl"
EXECUTOR_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/ExecutorWebService/ExecutorAPI?wsdl"
RELATION_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/RelationWebService/RelationAPI?wsdl"
REPORT_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/ReportWebService/ReportAPI?wsdl"
TASK_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/TaskWebService/TaskAPI?wsdl"
PROFILE_API_URL = "http://localhost:8080/wfe-service-4-SNAPSHOT/ProfileWebService/ProfileAPI?wsdl"
authentication_api = Client(AUTHENTICATION_API_URL)
execution_api = Client(EXECUTION_API_URL)
audit_api = Client(AUDIT_API_URL)
definition_api = Client(DEFINITION_API_URL)
executor_api = Client(EXECUTOR_API_URL)
relation_api = Client(RELATION_API_URL)
report_api = Client(REPORT_API_URL)
task_api = Client(TASK_API_URL)
profile_api = Client(PROFILE_API_URL)
user = authentication_api.service.authenticateByLoginPassword("Administrator", "wf")
def execution_api_test():
print("----------------------------")
print("Execution API")
print("----------------------------")
for process in execution_api.service.getProcesses(
user, create_batch_presentation("PROCESS", 1, 100, ["var", "count"],
[create_filter("definitionName", "long-process", False)],
[create_sorting("id", "asc")])):
print(process.id, process.name, print_var_names(process.variables))
print("----------------------------")
print(execution_api.service.getProcessesCount(
user, create_batch_presentation("PROCESS", 1, 100, [], [create_filter("definitionName", "long-process", True)], [])))
print("----------------------------")
def audit_api_test():
print("----------------------------")
print("Audit API")
print("----------------------------")
print(audit_api.service.getSystemLogs(user, create_batch_presentation("SYSTEM_LOG", 1, 100, [], [], [])))
print("----------------------------")
print(audit_api.service.getSystemLogsCount(user, create_batch_presentation("SYSTEM_LOG", 1, 100, [], [], [])))
print("----------------------------")
def definition_api_test():
print("----------------------------")
print("Definition API")
print("----------------------------")
for processDefinition in definition_api.service.getProcessDefinitions(
user, create_batch_presentation("DEFINITION", 1, 100, [], [], [create_sorting("name", "asc")]), True):
print(processDefinition.id, processDefinition.name)
print("----------------------------")
print(definition_api.service.getProcessDefinitionsCount(user, create_batch_presentation("DEFINITION", 1, 100, [], [], [])))
print("----------------------------")
def executor_api_test():
print("----------------------------")
print("Executor API")
print("----------------------------")
for executor in executor_api.service.getExecutors(
user, create_batch_presentation("EXECUTOR", 1, 100, [],
[create_filter("name", "Bots", True)],
[create_sorting("name", "asc")])):
print(executor.id, executor.name)
print("----------------------------")
print(executor_api.service.getExecutorsCount(user, create_batch_presentation("EXECUTOR", 1, 100, [], [], [])))
print("----------------------------")
for executor in executor_api.service.getGroupChildren(
user, create_executor(2), create_batch_presentation("EXECUTOR", 1, 100, [], [], []), False):
print(executor.id, executor.name)
print("----------------------------")
print(executor_api.service.getGroupChildrenCount(
user, create_executor(2), create_batch_presentation("EXECUTOR", 1, 100, [], [], []), True))
print("----------------------------")
for group in executor_api.service.getExecutorGroups(
user, create_executor(1), create_batch_presentation("GROUP", 1, 100, [], [], []), False):
print(group.id, group.name)
print("----------------------------")
print(executor_api.service.getExecutorGroupsCount(
user, create_executor(1), create_batch_presentation("GROUP", 1, 100, [], [], []), True))
print("----------------------------")
def relation_api_test():
print("----------------------------")
print("Relation API")
print("----------------------------")
print(relation_api.service.getRelations(user, create_batch_presentation("RELATION", 1, 100, [], [], [])))
print("----------------------------")
print(relation_api.service.getRelationPairs(user, "New relation", create_batch_presentation("RELATION", 1, 100, [], [], [])))
print("----------------------------")
def report_api_test():
print("----------------------------")
print("Report API")
print("----------------------------")
print(report_api.service.getReportDefinitions(user, create_batch_presentation("REPORTS", 1, 100, [], [], []), True))
print("----------------------------")
def task_api_test():
print("----------------------------")
print("Task API")
print("----------------------------")
for task in task_api.service.getMyTasks(user, create_batch_presentation("TASK", 1, 100, [], [], [])):
print(task.id, task.name)
print("----------------------------")
for task in task_api.service.getTasks(user, create_batch_presentation("TASK", 1, 100, [], [], [])):
print(task.id, task.name)
print("----------------------------")
execution_api_test()
audit_api_test()
definition_api_test()
executor_api_test()
relation_api_test()
report_api_test()
task_api_test()
# Пример выполнения демо-процесса 4.4.3+
Ниже рассмотрен пример выполнения демо-процесса "complete_task_sample" с использованием вызова веб-сервисов RunaWFE из клиента на языке Python.
Сценарий выполнения:
Пользователь Administrator запускает вручную через веб-интерфейс N экземпляров демо-процесса "complete_task_sample", на стартовой форме вводит значение переменной "Вопрос" формата "Строка". Управление переходит в задачу "Ввести ответ", в которой отображается ранее введенный "Вопрос", а также предлагается ввести "Ответ" (переменная формата "Флаг (логическое выражение)").
Далее пользователь запускает python скрипт, в котором с помощью вызова веб-сервисов RunaWFE осуществляется:
- получение списка задач "Ввести ответ" пользователя Administrator и значения переменной "Вопрос"
- передача полученного значения переменной в некоторую функцию для формирования ответа (True, False)
- инициализация переменной "Ответ" сформированным значением
- выполнение списка заданий "Ввести ответ" с вводом соответствующего значения переменной "Ответ"
Пользователь получает задание "Ознакомиться с положительным ответом"/"Ознакомиться с отрицательным ответом" в зависимости от значения переменной "Ответ".
В качестве SOAP клиента используется Zeep https://docs.python-zeep.org/. Получение списка задач, а также переменных выполняется с помощью вызова метода getMyTasks, в который передается настроенный профиль отображения BatchPresentation с фильтром по названию БП, задачи и переменной.
import random
import urllib.request
from zeep import Client
LOGIN = "Administrator"
PWD = "wf"
DEFINITION_NAME = "complete_task_sample"
TASK_NAME = "Ввести ответ"
VARIABLE_REQUEST_NAME = "Вопрос"
VARIABLE_RESPONSE_NAME = "Ответ"
def get_response(varable_value):
return random.choice([True, False])
def create_batch_presentation(presentation_type, page_number, page_size, variables, filters, sortings):
request = task_api.get_type('{http://impl.service.wfe.runa.ru/}wfBatchPresentation')()
request.classPresentationType = presentation_type
request.pageNumber = page_number
request.pageSize = page_size
request.variables = variables
request.filters = filters
request.sortings = sortings
return request
def create_filter(name, value, exclusive):
filter = execution_api.get_type("ns0:filter")()
filter.name = name
filter.value = value
filter.exclusive = exclusive
return filter
wfe_version = urllib.request.urlopen("http://localhost:8080/wfe/version").read().decode('utf-8')
BASE_URL = "http://localhost:8080/wfe-service-" + wfe_version
AUTHENTICATION_API_URL = BASE_URL + "/AuthenticationWebService/AuthenticationAPI?wsdl"
TASK_API_URL = BASE_URL + "/TaskWebService/TaskAPI?wsdl"
EXECUTION_API_URL = BASE_URL + "/ExecutionWebService/ExecutionAPI?wsdl"
authentication = Client(AUTHENTICATION_API_URL)
user = authentication.service.authenticateByLoginPassword(LOGIN, PWD)
task_api = Client(TASK_API_URL)
execution_api = Client(EXECUTION_API_URL)
for task in task_api.service.getMyTasks(
user, create_batch_presentation("TASK", 1, 100, [VARIABLE_REQUEST_NAME],
[create_filter("definitionName", DEFINITION_NAME, False), create_filter("name", TASK_NAME, False)], [])):
if task.variables:
print("ID экземпляра БП", DEFINITION_NAME, "-", task.processId)
print("Задание -", task.name)
variable_request = task.variables[0]
response_value = get_response(variable_request.value)
print("Переменные:")
print(" ", VARIABLE_REQUEST_NAME, "-", variable_request.value)
print(" ", VARIABLE_RESPONSE_NAME, "-", response_value)
variable_response = execution_api.get_type('{http://impl.service.wfe.runa.ru/}variable')()
variable_response.name = VARIABLE_RESPONSE_NAME
variable_response.value = format(response_value).lower()
task_api.service.completeTaskWS(user, task.id, [variable_response], task.owner.id)
print("Выполнено\n")
Выполнение:
ID экземпляра БП complete_task_sample - 37 Задание - Ввести ответ Переменные: Вопрос - Какой-то вопрос 1 Ответ - False Выполнено
ID экземпляра БП complete_task_sample - 38 Задание - Ввести ответ Переменные: Вопрос - Какой-то вопрос 2 Ответ - False Выполнено
ID экземпляра БП complete_task_sample - 39 Задание - Ввести ответ Переменные: Вопрос - Какой-то вопрос 3 Ответ - True Выполнено