Разработка расширения для DVWebTool и DVSupService

Программа DVWebTool устанавливается на клиентскую машину только в ОС Microsoft Windows. Для ОС Linux используется DVSupService, см. подробнее Установка и запуск службы DVSupService.

Ссылка на пример на GitHub: Watermark.

Расширения для DVSupService и DVWebTool можно распространять с помощью пакетов Debian, с подробной инструкцией и необходимыми материалами можно ознакомиться в репозитории на GitHub.

Дополнительным компонентом модуля Web-клиент является программа DVWebTool, которая предоставляет функциям Web-клиента доступ к устройствам и программному обеспечению, установленному на компьютере пользователя Web-клиента. Программа DVWebTool также может быть использована для обработки произвольных задач на пользовательском компьютере.

Программа DVWebTool является приложением для рабочего стола с возможностью расширения функциональности. Расширение DVWebTool представляет собой DLL-файл содержащий ожидаемый класс с веб-методами. Данные методы могут вызываться клиентскими компонентами Web-клиента (есть стандартный метод JSAPI для вызова) или другими способами.

В качестве примера будет разработано расширение, реализующее функцию добавления водяных знаков в PDF-файлы карточки Документ.

Алгоритм работы расширения:
  1. При вызове функции расширения Клиент передаёт идентификаторы PDF файлов карточки Документ (идентификаторы карточек Файл с версиями, а не идентификаторы собственно файлов) и идентификатор самой карточки Документ.

  2. Расширение DVWebTool загружает PDF-файлы на компьютер пользователя с помощью функций, предоставляемых Web-клиентом.

  3. Расширение DVWebTool добавляет в полученные PDF-файлы водяные знаки используя функции сторонней библиотеки для работы с PDF.

  4. Расширение DVWebTool прикрепляет полученные файлы с водяными знаками с помощью функций, предоставляемых Web-клиентом.

    Расширение не должно изменять существующие файлы карточки: полученные PDF-файлы с водяными знаками должны быть добавлены в карточку в качестве дополнительных.

Для реализации функций, указанных в пп. 2 и 4, потребуется разработать расширение Web-клиента с соответствующей функциональностью.

Также для примера необходимо разработать клиентское расширение, которое будет вызывать функции расширения DVWebTool с передачей им идентификаторов PDF-файлов Документа, в которые нужно добавить водяные знаки.

Полный код примера доступен в репозитории на GitHub.

Расширение Web-клиента

Для работы расширения DVWebTool необходимо, чтобы Web-клиент по запросу возвращал файл из карточки Файл с версиями с переданным идентификатором, а также мог прикреплять переданные файлы к карточке Документ.

Требуемая функциональность может быть реализована с помощью расширения Web-клиента.

Ниже приведена часть исходного кода контроллера, реализованного в серверном расширении.

Файл Others/Watermark/WatermarkServerExtension/Controllers/FileOperationsController.cs
        public HttpResponseMessage GetFile(Guid fileCardID)
        {
            FileReader fileReader = fileService.GetFileReader(fileCardID);

            if (fileReader.FileID == Guid.Empty)
                return new HttpResponseMessage(HttpStatusCode.NotFound);

            HttpResponseMessage response = CreateResponseForFile(fileReader);
            return response; (1)
        }

        [HttpPost]
        public async Task<ActionResult> AddFile([FromForm(Name = "file")] FormFileCollection formFiles, Guid cardId) (2)
        {

            string rootPath = CreateAndGetTempFolder();
            try
            {
                List<string> files = await SaveFilesFromResponse(formFiles); (3)

                await fileService.AddFilesToCard(cardId, files);
                return Ok();

            }
            catch (Exception e)
            {
                return Problem(statusCode: (int)HttpStatusCode.InternalServerError, detail: e.Message);
            }
        }
1 Создаёт ответ с файлом.
2 Добавляет в карточку файлы из запроса.
3 Добавляемые файлы.

В контроллере используется сервис IFileService, реализация которого приведена ниже. Код дополнительных методов смотрите в полном примере на GitHub.

Файл Others/Watermark/WatermarkServerExtension/Services/FileService.cs
    public class FileService : IFileService
    {
        private readonly ICurrentObjectContextProvider currentObjectContextProvider;
        private Guid DOCUMENT_CARD_ID = new Guid("B9F7BFD7-7429-455E-A3F1-94FFB569C794");

        public FileService(ICurrentObjectContextProvider currentObjectContextProvider) {
            this.currentObjectContextProvider = currentObjectContextProvider;
        }

        public async System.Threading.Tasks.Task<IEnumerable<Guid>> AddFilesToCard(Guid cardID, List<string> files) (1)
        {
            var document = GetDocumentCard(cardID); (2)
            ObjectContext objectContext = GetObjectContext();

            ILockService lockService = GetLockService(objectContext);

            if (lockService.IsObjectLockedByAnotherUser(document)) {
                throw new Exception($"Карточка {cardID} заблокирована другим пользователем");
            }

            if (lockService.LockObjectBase(document) == false) {
                throw new Exception($"Не удалось заблокировать карточку {cardID}");
            }

            IDocumentService documentService = GetDocumentService(objectContext);

            return await System.Threading.Tasks.Task.Run(() =>
            {
                IEnumerable<Guid> documentFileIds;
                try
                {
                    IEnumerable<DocumentFile> documentsFiles = documentService.AddAdditionalFiles(document, files);
                    objectContext.AcceptChanges();
                    documentFileIds = documentsFiles.Select(t => t.FileId);
                }
                catch (Exception ex)
                {
                    throw new Exception($"Ошибка при добавлении файлов в карточку {cardID}\n {ex.Message}");
                }
                finally {
                    lockService.UnlockObject(document);
                }

                return documentFileIds; (3)
            });
        }

        public FileReader GetFileReader(Guid fileCardID) (4)
        {
            ObjectContext objectContext = GetObjectContext();
            IVersionedFileCardService versionedFileCardService = GetVersionedFileCardService(objectContext);

            VersionedFileCard fileCard = versionedFileCardService.OpenCard(fileCardID); (5)

            Guid fileID = fileCard.CurrentVersion.Id; (6)

            UserSession userSession = GetUserSession();

            if (userSession.FileManager.FileExists(fileID) == false)  (7)
                return new FileReader();

            var file = userSession.FileManager.GetFile(fileID); (8)

            return new FileReader() { (9)
                FileID = fileID,
                FileName = file.Name,
                Stream = file.OpenReadStream()
            };
        }
1 Добавляет файлы с файловой системы в карточку cardID.
2 Получает карточку, к которой прикрепляются файлы.
3 Возвращает идентификаторы добавленных файлов с версиями.
4 Получает указатель для чтения файла из карточки файла с версиями fileCardID.
5 Получение файла карточки с версиями.
6 Получение идентификатора файла из последней версии.
7 Если файла нет, возвращается пустой указатель.
8 Запрашивается файл текущей версии.
9 Возвращается указатель для чтения файла.

Расширение программы DVWebTool

Когда реализовано расширение Web-клиента, предоставляющее и записывающее файлы карточек, может быть реализовано расширение DVWebTool, использующее данные функции.

Расширение DVWebTool представляет собой сборку, в которой реализован интерфейс Docsvision.DVWebTool.WebServices.IServiceManager. Данный интерфейс определяет метод Register, который регистрирует контроллеры с необходимыми функциями во внутреннем веб-сервере DVWebTool, и поле DisplayName с названием расширения.

Ниже приведён код класса, реализующего интерфейс IServiceManager в данном примере.

Файл Others/Watermark/WatermarkWebToolExtension/WatermarkManager.cs
    public class WatermarkManager : IServiceManager
    {
        public string DisplayName => "Watermark to PDF"; (1)

        public void Register(WebSocketSharper.Server.WebSocketServer server) (2)
        {
            server.AddWebSocketService<WatermarkController>("/Watermark"); (3)
        }
    }
1 Название расширения для информации в окне "О программе".
2 Регистрация контроллера расширения.
3 Регистрация контроллера PDFWatermarkController для маршрута Watermark.

В данном примере выполняется регистрация контроллера WatermarkController для обработки запросов, поступающих по пути /Watermark.

Контроллер, передаваемый в AddWebSocketService, должен быть производным типа Docsvision.DVWebTool.WebServices.BaseService. При его реализации необходимо зарегистрировать в BaseService.actions веб-методы, с помощью которых будут вызываться функции DVWebTool, предоставляемые расширением. Данные методы будут доступны для вызова по протоколу WebSocket по адресу ws://localhost:/%Адрес контроллера%/%Название метода%.

Ниже приведена часть реализации контроллера WatermarkController, содержащего методы обработки входящих запросов на добавление водяного знака.

Файл Others/Watermark/WatermarkWebToolExtension/WatermarkController.cs
    public class WatermarkController : BaseService
    {
        private readonly ServiceProvider serviceProvider;
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        private string WATERMARK = "Секретно"; (1)

        public WatermarkController()
        {
            serviceProvider = new ServiceProvider();
            Init();
        }

        private void Init() (2)
        {
            actions.Add(nameof(AddWatermarkToFiles), AddWatermarkToFiles);
        }

        private WebServiceResponse AddWatermarkToFiles(WebServiceRequest webServiceRequest, JObject data) (3)
        {
            Logger.Info("Получено задание на добавление водяного знака");

            if (data == null)
            {
                return CreateBadResponse("С клиента не переданы данные для работы");
            }

            AddWatermarkRequest request; (4)
            try
            {
                request = data.ToObject<AddWatermarkRequest>();
            }
            catch
            {
                Logger.Error($"Ошибка преобразования полученного сообщения: {data}");
                return CreateBadResponse("Поступивший запрос не соответствует ожидаемому формату");
            }

            string doneInfo;

            try
            {
                doneInfo = HandleRequest(request).Result;
            }
            catch (Exception ex)
            {
                return CreateBadResponse(ex.Message);
            }

            return CreateEndProcessResponse(request.CardID, $"Водяные знаки добавлены в файлы:<p/>{doneInfo}");
        }


        private async Task<string> HandleRequest(AddWatermarkRequest request) (5)
        {
            var connectionService = new ConnectionToWebClient(request.ServerAddress, request.AccessToken);

            List<string> files = new List<string>();

            foreach (var fileId in request.FileIDs) (6)
            {
                try
                {
                    string pathToFile = await connectionService.PullFile(fileId);
                    files.Add(pathToFile);
                }
                catch (Exception ex)
                {
                    Logger.Error(ex.Message);
                    throw new Exception($"Не удалось получить из карточки файл с идентификатором {fileId}");
                }
            }

            var watermarkService = new WatermarkService(); (7)

            List<Task<string>> processes = new List<Task<string>>(); (8)
            string doneInfo = "";

            foreach (var file in files)
            {
                try
                {
                    processes.Add(watermarkService.AddWatermark(file, WATERMARK));
                    doneInfo += $" {Path.GetFileName(file)}<p/>";
                }
                catch (Exception ex)
                {
                    Logger.Error(ex.Message);
                    throw new Exception($"Не удалось добавить водяной знак в файл {Path.GetFileName(file)}");
                }
            }

            var filesWithWatermark = await Task.WhenAll(processes);

            try
            {
                await connectionService.PushFiles(request.CardID, filesWithWatermark); (9)
            }
            catch (Exception ex)
            {
                Logger.Error(ex.Message);
                throw new Exception($"Не удалось сохранить файлы в карточке");
            }
            return doneInfo;
        }
1 Текст водяного знака.
2 Регистрация методов контроллера PDFWatermarkController. Название метода регистрозависимое.
3 Веб-метод добавления водяного знака. Метод должен принимать два параметра: WebServiceRequest и JObject. Данные передаются в data.
4 Загружаем данные из полученного запроса в модель AddWatermarkRequest.
5 Обработчик запроса на добавление водяного знака. Получает данные запроса. Возвращает строку с названиями файлов, в которые добавлены запросы.
6 Загружаем с Web-клиента файлы, идентификаторы которых переданы в запросе.
7 Сервис для работы с водяными знаками.
8 Добавление водяных знаков в файлы из списка files.
9 Отправка запроса на прикрепление файлов filesWithWatermark к карточке request.CardID.
Контроллер WatermarkController использует функции двух сервисов:
  • ConnectionToWebClient для получения файлов из карточек Файл с версиями и загрузки файлов в карточки Документ. Данный сервис использует функции, реализованного серверного расширения Web-клиента.

  • WatermarkService для добавления водяных знаков в PDF файлы.

Реализации данных сервисов смотрите в исходных кодах примера на GitHub.

После публикации расширения DVWebTool, зарегистрированный в расширении метод AddWatermarkToFiles контроллера Watermark может быть вызван из клиентского расширения Web-клиента с помощью сервиса DVWebToolConnection.

Клиентское расширение

В качестве примера использования функций расширения Watermark to PDF программы DVWebTool было разработано клиентское расширение, которое выполняет две задачи:

  • Получает из текущей открытой карточки Документа идентификаторы основных файлов формата PDF.

  • Отправляет идентификаторы файлов расширению Watermark to PDF.

Общие требования к реализации клиентских расширений приведены в пункте "Расширение возможностей клиентской части Web-клиента".

Прежде всего реализуем сервис, получающий список идентификаторов файлов, в которые нужно добавить водяной знак и передавать его в метод AddWatermarkToFiles расширения Watermark to PDF. Ниже приведён исходный код данного сервиса.

Файл Others/Watermark/WatermarkWebExtension/src/WatermarkService.tsx
export class WatermarkService { (1)

    constructor(private services: $RequestManager & $WebServices & $ApplicationSettings
        & $MessageBox & $SiteUrl & $CurrentEmployeeId & $JwtTokenController) { (2)
    }

    async AddWatermarkToFiles(cardID: string, fileIDs: string[]): Promise<IWebServicesResponse<any>> { (3)

        const accessToken = await this.services.jwtTokenController.getAccessToken(); (4)

        const data: any = { (5)
            data: {
                cardID: cardID,
                fileIDs: fileIDs,
                userID: this.services.currentEmployeeId,
                serverAddress: this.services.siteUrl,
                accessToken
            },
            action: 'AddWatermarkToFiles', (6)
            locale: this.services.applicationSettings.culture.twoLetterISOLanguageName (7)
        };

        return DVWebToolConnection.trySendData("Watermark", data, this.services); (8)
    }
}

export type $WatermarkService = { watermarkService: WatermarkService }; (9)
export const $WatermarkService = serviceName((s: $WatermarkService) => s.watermarkService);
1 Клиентский сервис, предоставляющий доступ к методу добавления водяных знаков, предоставляемому расширением DVWebTool.
2 Метод принимает идентификатор карточки и идентификатор её конвертируемых файлов.
3 Токен доступа, с помощью которого DVWebTool будет выполнять запросы к серверу Web-клиента.
Обратите внимание, время жизни токена ограничено! Подробнее см. ниже.
4 В данных нужно также передать:
  • Адрес сервера Web-клиента. DVWebTool должен подключиться к Web-клиенту для получения и сохранения файлов карточки.

  • ID пользователя для отправки оповещения о завершении процесса

5 Название метода, вызываемого из расширения DVWebTool.
6 Обязательное для передачи название локали.
7 Вызываем метод AddWatermarkToFiles из контроллера Watermark расширения DVWebTool. Тип DVWebToolConnection предоставляет методы для работы с DVWebTool.
8 Регистрируем сервис WatermarkService.

Сервис $WatermarkService предоставляет единственный метод AddWatermarkToFiles, который вызывает функцию программы DVWebTool с помощью метода DVWebToolConnection.trySendData. При вызове данного метода нужно передать название контроллера и данные, в которых должно быть название вызываемого метода контроллера — в поле action передаваемых данных.

В качестве метода, использующего сервис $WatermarkService реализуем обработчик нажатия кнопки разметки карточки Документ, который будет получать идентификаторы основных PDF-файлов карточки, вызывать метод $WatermarkService.AddWatermarkToFiles и после завершения его работы обновлять содержимое элемента управления Список файлов, или отображать ошибку.

Файл Others/Watermark/WatermarkWebExtension/src/EventHandlers.tsx
export async function addWatermark(sender: LayoutControl) { (1)
   showNotify("Запущен процесс добавления водяных знаков");

   let cardId = sender.layout.getService($CardId); (2)
   let files = sender.layout.getService($FileService).getFiles();

   let fileIDs = new Array();

   files.forEach((item) => { (3)
      if (item.data.isMain && item.data.name.toLowerCase().endsWith(".pdf")) { (4)
         fileIDs.push(item.data.fileId);
      }
   })

   let watermarkService = sender.layout.getService($WatermarkService); (5)

   let response = await watermarkService.AddWatermarkToFiles(cardId, fileIDs); (6)

   if (response.success == false) {
      showError(response.errorMessage);
   } else {
      showNotify(response.data.message);

      if (layoutManager.cardLayout == null)
         return;

      let currentCardId = sender.layout.getService($CardId);
      if (currentCardId == cardId) {
         let fileList = layoutManager.cardLayout.controls.get<FileListControl>("fileList"); (7)
         await fileList.reloadFromServer();
      }
   }
}
1 Обработчик для события нажатия иконки добавления водяных знаков.
2 Получаем ID текущей карточки и список файлов из элемента FileList.
3 Получаем из модели files только идентификаторы файлов.
4 Обрабатываем только основные файлы с расширением PDF.
5 Получаем реализованный сервис для работы с водяными знаками.
6 Вызываем функцию добавления водяных знаков для файлов с ИД из списка fileIDs.
7 Обновляем список файлов, если есть. Возможно уже открыта другая карточка, но в данном случае это не существенно.

Настройка времени действия токена

Время жизни токена доступа зависит от значений параметров JwtAccessTokenLifetime и JwtClockSkew в конфигурационном файле модуля.

"Docsvision": {
    "WebClient": {
      "SettingGroups": {
        "System": {
          "JwtAccessTokenLifetime": 300, (1)
          "JwtClockSkew": 300 (2)
        }
      }
    }
  }
1 Время жизни токена. По умолчанию 5 минут (300 секунд).
2 Допустимая погрешность часов при проверке времени жизни токена. Значение прибавляется к JwtAccessTokenLifetime. По умолчанию 5 минут (300 секунд).

Сборка проекта

Сборка серверного расширения Web-клиента и расширения DVWebTool.
  1. Откройте решение Samples.sln.

  2. Соберите проект Other  Watermark  WatermarkServerExtension.

  3. Соберите проект Other  WatermarkWebToolExtension.

Сборка клиентской части.
  1. Откройте в командной строке папку Others  Watermark  WatermarkWebExtension.

  2. Выполните команды:

    npm install
    npm update
    npm run build:prod
Публикация компонентов на сервере Web-клиент.
  1. Остановите dvwebclient.

  2. Скопируйте папку SamplesOutput/Content/Modules/WatermarkWebExtension/ в /lib/docsvision/webclient/Content/Modules.

  3. Скопируйте папку SamplesOutput/Extensions/WatermarkServerExtension в /lib/docsvision/webclient/Extensions.

  4. Для работы пользователей на Windows скопируйте файлы из папки SamplesOutput/Site/Content/Tools/DVWebTool/Application Files/ в Путь к папке с панелью управления Web-клиентом/Site/Content/Tools/DVWebTool/, например C:/Program Files (x86)/Docsvision6/WebClient/Site/Content/Tools/DVWebTool.

  5. Запустите dvwebclient.

Регистрация расширения DVWebTool на сервере Web-клиента.
  1. Запустите программу Панель управления Web-клиентом.

  2. Нажмите кнопку Собрать DVWebTool.

  3. В диалоге введите адрес публикации Web-клиента, при необходимости установите другие опции, и нажмите кнопку ОК.

  4. Установите собранную утилиту DvWebTool на сервер Web-клиента, см. подробнее "Установка и запуск программы DVWebTool.

  5. Перезапустите dvwebclient.

Сборка и установка пакета для DvSupService

Для работы пользователей на Linux нужно обеспечить копирование файлов расширения в папку установки DvSupService — /lib/docsvision/dvsupservice. Рекомендуемый способ это сделать — собрать deb-пакет[1] и распространить его любым удобным способом:

  1. Убедитесь, что собран проект Other > Watermark > WatermarkWebToolExtension. В результате в папке Others/Watermark/DebPackage/Build/DvSupServiceExtension должны оказаться файлы расширения в папках Assemblies и Services. Параметр ExtensionsPath в конфигурационном файле DVSupService позволяет изменять папку для расширений, по-умолчанию это Assemblies.

  2. Скопировать папку Others\Watermark\DebPackage на Linux в каталог, где планируется выполнять сборку пакета (расположение папки значения не имеет).

  3. Установите необходимые для сборки компоненты следующей командой:

    sudo apt-get install make debhelper dpkg-dev
  4. Откройте консоль в папке Build и соберите пакет с помощью следующей команды:

    cd DebPackage/Build make

    В результате в папке DebPackage появится файл dvsupservice-extension_1.0_amd64.deb.

  5. Остановите сервис dvsupservice командой:

    sudo systemctl stop dvsupservice
  6. Распространите и установите на машины пользователей собранный пакет. Для этого можно создать репозиторий, чтобы можно было установить расширение с помощью команды sudo apt-get install dvsupservice-extension. См. "Создание простого репозитория". Либо просто скопируйте файл на клиентскую машину и выполните команду:

    sudo dpkg -i dvsupservice-extension_1.0_amd64.deb
  7. Запустите сервис dvsupservice командой:

    sudo systemctl start dvsupservice

Проверка примера

Настройте разметку:
  1. В программе Конструктор Web-разметок добавьте элемент Кнопка в любую разметку просмотра карточки Документ.

  2. Укажите для события При щелчке обработчик addWatermark.

  3. Сохраните разметку.

  4. Установите или обновите программу DVWebTool. Cм. пункт Установка и запуск программы DVWebTool в руководстве пользователя Web-клиент.

  5. Запустите программу DVWebTool. Убедитесь, что программа DVWebTool и Web-клиент будут запущены от имени одного пользователя.

Убедитесь, что все расширения установлены:
  1. В Web-клиенте перейдите в раздел О программе.

    В разделе Подключенные расширения должны быть указаны расширения:
    • WatermarkServerExtension (Сборка <номер сборки>).

    • Watermark to PDF <номер сборки> — web-расширение.

  2. Откройте панель About из меню Docsvision DVWebTool.

    В списке установленных расширений должно быть расширение:
    • Watermark to PDF <версия>.

  3. Откройте для просмотра любую карточку с разметкой, настроенной в п. 1.

  4. Добавьте один или несколько основных файлов с расширением .pdf.

  5. Нажмите на добавленную кнопку с обработчиком addWatermark.

Появится сообщение Запущен процесс добавления водяных знаков — начнётся процесс добавления водяных знаков.

После завершения процедуры появится сообщение Водяные знаки добавлены в файлы: "список PDF-файлов, в которые добавлены водяные знаки".

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

Особенности обновления

При обновлении версии Web-клиента регистрация расширения в программе DVWebTool будет отменена (конфигурационные файлы программы перезаписываются).

После установки новой версии Web-клиента необходимо повторно зарегистрировать расширения DVWebTool на сервере Web-клиент.


1. Репозиторий Docsvision с примерами пакетов Debian расположен на GitHub.