Перейти к содержанию

Создание скриптов для консольной команды tadmin Script

Создание скриптов для консольной команды tadmin Script

Начиная со сборки платформы 3.6 добавлена возможность создавать скрипты tadmin - специальные классы, которые запускает команда tadmin Script для выполнения кода .NET. Классы располагаются в сборках расширений, мы рекомендуем использовать сборку Tessa.Extensions.Console. Скрипты могут получать именованные аргументы, которые используются в классе для параметризации его действий.

Синтаксис команды tadmin Script доступен в руководстве администратора.

Вместо написания скрипта можно создать собственную команду с произвольными параметрами, как было описано выше, но скрипты значительно проще писать - обычно это класс с единственным методом, в котором разработчик автоматически получает контейнер Unity со всеми зависимостями, способ установки соединения с БД, и открытую сессию с веб-сервисом (опционально). Также скрипты удобны, когда необходимо выполнить некоторые действия при установке обновлений, т.е. для написания миграций.

Клиентские скрипты

Клиентские скрипты выполняют логин (открытие сессии) на сервер приложений в соответствии с параметрами, передаваемыми в командной строке. По завершении работы скрипта выполняется закрытие сессии.

Классу скрипта доступны следующие свойства:

  1. Result - код возврата, который получает сценарий, вызывающий команду tadmin. По умолчанию равен 0.

  2. Logger - объект, используемый для логирования с использованием консоли.

  3. Container - контейнер Unity, содержащий клиентские зависимости.

В сборке расширений Tessa.Extensions.Console создадим папку Scripts, в которой будут располагаться классы со скриптами. Добавим в неё класс с именем NewCard, который будет содержать скрипт создания структуры карточки с выводом её в окно консоли (или на стандартный вывод, который может быть перенаправлен в файл).

Tip

Вы можете использовать любую структуру папок в любых проектах расширений для определения классов-скриптов. Требования к классу-скрипту в том, чтобы он использовал атрибут [ConsoleScript] и реализовывал интерфейс IConsoleScript (а лучше - наследовался от базового класса ConsoleScriptBase или от одного из его наследников). Использование проекта Tessa.Extensions.Console и папки Scripts - это рекомендация.

Класс клиентского скрипта должен быть унаследован от базового класса ClientConsoleScriptBase. Атрибут [ConsoleScript] является обязательным для того, чтобы утилита tadmin определила этот класс как класс скрипта, также в атрибуте указывается имя скрипта, по которому он будет вызван.

using System; using System.Threading; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Platform.ConsoleApps; using Unity;

namespace Tessa.Extensions.Console.Scripts { [ConsoleScript(nameof(NewCard))] public class NewCard : ClientConsoleScriptBase { protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken) { string typeName = this.TryGetParameter("TypeName"); if (string.IsNullOrEmpty(typeName)) { await this.Logger.ErrorAsync("Pass 'TypeName' parameter for card type name to create."); this.Result = -1; return; }

var request = new CardNewRequest { CardTypeName = typeName }; var response = await this.Container.Resolve<ICardRepository>().NewAsync(request, cancellationToken);

var result = response.ValidationResult.Build(); await this.Logger.LogResultAsync(result);

if (!result.IsSuccessful) { this.Result = -2; return; }

response.Card.ID = Guid.NewGuid();

await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true)); }

protected override async ValueTask ShowHelpCoreAsync(CancellationToken cancellationToken = default) { await this.Logger.WriteLineAsync( $"Example: {Assembly.GetEntryAssembly()?.GetName().Name} Script {nameof(NewCard)}" + " -pp:TypeName=Partner"); } } }

Скрипт будет вызываться по имени NewCard. Имена всех скриптов должны быть уникальными, поэтому рассмотрите добавление префикса к имени, например, AbNewCard. Если вы не укажите имя в атрибуте [ConsoleScript], то скрипт можно будет вызвать, указав полное имя типа, а именно Tessa.Extensions.Console.Scripts.NewCard.

Скрипт должен получить обязательный параметр TypeName с алиасом типа карточки, который создаётся. Если параметр отсутствует, то будет возвращён код возврата ErrorLevel = -1 (по логике в самом скрипте), что можно будет отследить из файла скрипта .bat или .sh.

Пример использования скрипта выводится на экран в методе ShowHelpCoreAsync, который вызывается при передаче параметра --help или -help.

.\tadmin Script NewCard --help

Скомпилируйте и замените файл Tessa.Extensions.Console в подпапке extensions внутри папки с tadmin. Запустите скрипт следующей командой (параметры подключения укажите в соответствии с вашим сервером приложений):

.\tadmin Script NewCard -pp:TypeName=Partner -a:https://localhost/tessa -u:admin -p:admin -q >partner.txt

Tip

Параметр -q блокирует вывод информационных сообщений, оставляя только ошибки и вывод методов Logger.WriteLineAsync.

Tip

Имя скрипта NewCard и имя параметров -pp:Parameter=Value могут указываться без учёта регистра, в т.ч. при выполнении на Linux.

Если команда выполняется без ошибок, то в текущей папке создаётся файл partner.txt, в котором указана структура новой карточки “Контрагент”. Откройте файл в блокноте, в секции “Partners” для поля “Name” укажите любую строку (в кавычках), содержащую краткого имени контрагента - это обязательное поле в карточке “Контрагент”. Вы также можете указать другие поля по желанию.

Мы используем этот файл в новом скрипте CreateCard, создайте класс с таким именем в папке Scripts.

using System.Threading; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Platform.ConsoleApps; using Unity;

namespace Tessa.Extensions.Console.Scripts { [ConsoleScript(nameof(CreateCard))] public class CreateCard : ClientConsoleScriptBase { protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken) { var json = await this.Options.Input.ReadToEndAsync(); var card = new Card().FromTypedJson(json);

var request = new CardStoreRequest { Card = card }; var response = await this.Container.Resolve<ICardRepository>().StoreAsync(request, cancellationToken);

var result = response.ValidationResult.Build(); await this.Logger.LogResultAsync(result);

this.Result = result.IsSuccessful ? 0 : -1; } } }

Команда считывает карточку из стандартного ввода и сохраняет карточку в веб-сервисе от имени указанного пользователя. Посредством вызова type partner.txt мы выводим карточку из файла partner.txt на стандартный вывод, который перенаправляется на стандартный ввод команды tadmin Script.

type partner.txt|.\tadmin Script CreateCard -a:https://localhost/tessa -u:admin -p:admin

Клиентские скрипты с подключением к БД

Помимо прямого подключения к веб-сервису та же самая команда может использовать строку подключения в app.json для прямого подключения к базе данных, чтобы выполнить на БД запросы в обход веб-сервиса.

В проекте Tessa.Extensions.Console в папке Scripts создайте класс GetCurrency. Запрос к базе данных будет выполнять поиск идентификатора карточки валюты по имени, указанному в параметре “Name”. Карточка по найденному идентификатору загружается и выводится на консоль в виде json-структуры.

using System; using System.Threading; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Platform.ConsoleApps; using Tessa.Platform.Data; using Unity;

namespace Tessa.Extensions.Console.Scripts { [ConsoleScript(nameof(GetCurrency))] public class GetCurrency : ClientConsoleScriptBase { protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken) { string name = this.TryGetParameter("name"); if (string.IsNullOrEmpty(name)) { await this.Logger.ErrorAsync("Pass 'name' parameter for currency name."); this.Result = -1; return; }

Guid? cardID;

(DbManager db, IQueryBuilderFactory builder) = await this.ConnectDbAsync(cancellationToken); await using (db) { cardID = await db .SetCommand( builder .Select().C("ID") .From("Currencies").NoLock() .Where().LowerC("Name").Equals().LowerP("Name") .Build(), db.Parameter("Name", name)) .LogCommand() .ExecuteAsync<Guid?>(cancellationToken); }

if (!cardID.HasValue) { await this.Logger.ErrorAsync("Can't find currency by name: '{0}'", name); this.Result = -2; return; }

var cardRepository = this.Container.Resolve<ICardRepository>(); var response = await cardRepository.GetAsync(new CardGetRequest { CardID = cardID }, cancellationToken);

var result = response.ValidationResult.Build(); await this.Logger.LogResultAsync(result);

if (!result.IsSuccessful) { this.Result = -3; return; }

await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true)); } } }

Note

Рекомендуемым способом решать такую задачу было бы выполнение представления через IViewService, но для примера выполняется прямой запрос к БД.

Скомпилируйте и замените файл Tessa.Extensions.Console.dll для замены подпапки extensions. Выполните следующую команду, чтобы вывести структуру карточки “Валюта” для валюты “RUB” (рубли, поиск без учёта регистра).

.\tadmin Script GetCurrency -pp:Name=RUB -a:https://localhost/tessa -u:admin -p:admin -q

Tip

Укажите параметр командной строки -cs:connectionString для использования строки подключения с указанным именем вместо строки по умолчанию. Параметр -db:databaseName может быть указан для изменения имени базы данных в строке подключения.

Если вы укажете несуществующую валюту, то скрипт выведет ошибку и вернёт ненулевой код возврата (в соответствии с бизнес-логикой метода ExecuteCoreAsync):

.\tadmin Script GetCurrency -pp:Name=RUB111 -a:https://localhost/tessa -u:admin -p:admin -q

Can't find currency by name: 'RUB111'

Серверные скрипты

Серверные скрипты имеют полностью инициализированный серверный контейнер IUnityContainer, который по умолчанию определяет сессию пользователя System (её можно заменить с использованием метода SessionContext.Create). При этом не выполняется логина к веб-сервису, и все параметры для логина игнорируются.

Warning

Серверный контейнер содержит только те расширения, которые подключены в extensions.xml. Для стандартной конфигурации команды tadmin это платформенные расширения (Server/Shared), серверные Shared-расширения типового решения из сборки Tessa.Extensions.Default.Shared (как правило, это расширения на метаинформацию) и серверные Shared-расширения проектного решения Tessa.Extensions.Shared. Для полноценной серверной работы, включая все расширения типового решения (правила доступа, маршруты и др.) мы рекомендуем использовать клиентские скрипты, или же подключать сборки с Server-расширениями в extensions.xml.

Классу скрипта доступны следующие свойства:

  1. Result - код возврата, который получает сценарий, вызывающий команду tadmin. По умолчанию равен 0.

  2. Logger - объект, используемый для логирования с использованием консоли.

  3. Container - контейнер Unity, содержащий серверные зависимости.

В проекте Tessa.Extensions.Console в папке Scripts добавьте файл GetCurrencySql, который будет содержать скрипт, аналогичный GetCurrency, но выполняемый на прямом подключении к базе данных с ограниченным набором расширений. Серверный скрипт должен наследоваться от базового класса ServerConsoleScriptBase в дополнение к атрибуту [ConsoleScript] с именем скрипта.

using System; using System.Threading; using System.Threading.Tasks; using Tessa.Cards; using Tessa.Platform.ConsoleApps; using Tessa.Platform.Data; using Unity;

namespace Tessa.Extensions.Console.Scripts { [ConsoleScript(nameof(GetCurrencySql))] public class GetCurrencySql : ServerConsoleScriptBase { protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken) { string name = this.TryGetParameter("name"); if (string.IsNullOrEmpty(name)) { await this.Logger.ErrorAsync("Pass 'name' parameter for currency name."); this.Result = -1; return; }

Guid? cardID;

var dbScope = this.Container.Resolve<IDbScope>(); await using (dbScope.Create()) { cardID = await dbScope.Db .SetCommand( dbScope.BuilderFactory .Select().C("ID") .From("Currencies").NoLock() .Where().LowerC("Name").Equals().LowerP("Name") .Build(), dbScope.Db.Parameter("Name", name)) .LogCommand() .ExecuteAsync<Guid?>(cancellationToken); }

if (!cardID.HasValue) { await this.Logger.ErrorAsync("Can't find currency by name: '{0}'", name); this.Result = -2; return; }

var cardRepository = this.Container.Resolve<ICardRepository>(); var response = await cardRepository.GetAsync(new CardGetRequest { CardID = cardID }, cancellationToken);

var result = response.ValidationResult.Build(); await this.Logger.LogResultAsync(result);

if (!result.IsSuccessful) { this.Result = -3; return; }

await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true)); } } }

Скрипт во много похож на предыдущий, но для выполнения прямых запросов к БД используется объект IDbScope.

Скомпилируйте и замените файл Tessa.Extensions.Console.dll в подпапке extensions. Выполните команду:

.\tadmin Script GetCurrencySql -pp:Name=RUB -q

В окне консоли будет выведена структура карточки “Валюта”.

Скрипты без настроенного контейнера Unity

Вы можете унаследовать класс скрипта от базового класса ConsoleScriptBase, чтобы не выполнялось никаких действий по настройке контейнера Unity. На основании этого класса реализации ClientConsoleScriptBase и ServerConsoleScriptBase предоставляют удобные средства для написания скриптов. Вы можете использовать базовый класс, если необходимо по-особому инициализировать объект контейнера Unity. В общем случае мы не рекомендуем этого делать.

Классу скрипта доступно свойство:

  1. Result - код возврата, который получает сценарий, вызывающий команду tadmin. По умолчанию равен 0.

Скрипт по-прежнему использует атрибут [ConsoleScript] для указания на то, что это скрипт, и для задания его имени. Обратите внимание на то, что переопределяемый метод здесь называется ExecuteAsync, а не ExecuteCoreAsync, как в других типах скриптов.

В проекте Tessa.Extensions.Console в папке Scripts создайте класс GetCurrencyId. В этом примере по имени валюты, переданной в параметре -pp:Name=..., определяется идентификатор карточки запросом к БД.

using System; using System.Threading; using System.Threading.Tasks; using Tessa.Platform.ConsoleApps; using Tessa.Platform.Data; using Unity;

namespace Tessa.Extensions.Console.Scripts { [ConsoleScript(nameof(GetCurrencyId))] public class GetCurrencyId : ConsoleScriptBase { protected override async ValueTask ExecuteAsync(CancellationToken cancellationToken) { var container = new UnityContainer().RegisterConsoleOperationLogger(this.Options); var logger = container.Resolve<IConsoleLogger>();

string name = this.TryGetParameter("name"); if (string.IsNullOrEmpty(name)) { await logger.ErrorAsync("Pass 'name' parameter for currency name."); this.Result = -1; return; }

Guid? cardID;

await using (DbManager db = await ConsoleAppHelper.CreateDbManagerAsync(logger, this.Options, cancellationToken)) { var builder = new QueryBuilderFactory(db.GetDbms());

cardID = await db .SetCommand( builder .Select().C("ID") .From("Currencies").NoLock() .Where().LowerC("Name").Equals().LowerP("Name") .Build(), db.Parameter("Name", name)) .LogCommand() .ExecuteAsync<Guid?>(cancellationToken); }

if (!cardID.HasValue) { await logger.ErrorAsync("Can't find currency by name: '{0}'", name); this.Result = -2; return; }

await logger.WriteLineAsync(cardID.Value.ToString()); } } }

  1. Для получения объекта логирования IConsoleLogger вызывается метод-расширение RegisterConsoleOperationLogger(this.Options) для контейнера IUnityContainer.

  2. Для подключения к БД используется метод ConsoleAppHelper.CreateDbManagerAsync(logger, this.Options, cancellationToken).

Скомпилируйте и замените файл Tessa.Extensions.Console.dll в подпапке extensions в папке tadmin. Выполните команду:

.\tadmin Script GetCurrencyId -pp:Name=RUB -q

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

.\tadmin Script GetCurrencyId -pp:Name=RUB -cs:migration -db:tessa_new -q

Back to top