decorate image
decorate image
В связи с техническими работами сайт будет недоступен 21 декабря 2024 с 12:00 до 12:30 МСК
Все статьи

Разработка системы документооборота: от идеи до внедрения

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

В банке уже используется CRM система и поэтому требуется DMS, которую можно будет интегрировать с ней по протоколу RabbitMQ.

Силами сторонних интеграторов банк пытался разработать собственную систему документооборота под эти потребности на основе SharePoint, но по объективным и не очень причинам с этим не получилось.

Одной из самых популярных технологий для разработки веб-сервисов на сегодняшний день является фреймворк Spring Boot. Однако, он сам по себе не предоставляет собой готового решения или архитектуры, являясь инструментом связывания компонентов и технологий различных стандартов и производителей. Подбор этих технологий связан с рисками получить не вполне подходящий для разработки продукт, части которого могут со временем потребовать замены или выражаясь в терминах разработки переписывания “с нуля”. Но существуют решения на базе технологий фреймворка Spring, которые закрывают полный спектр потребностей разработки бизнес-решений. Одним из таких решений является платформа Jmix, которая, сохраняя гибкость и расширяемость Spring дополняет его возможностями и эффективностью технологий создания пользовательского интерфейса Vaadin. Это позволяет разработчикам создавать код используя один язык программирования Java, в котором нет явно выраженного разделения между фронтенд и бекенд составляющими, фокусируясь на реализации требований, а не технических проблем.

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

Создаем проект

Для работы с Jmix нам понадобится установленная на локальном компьютере среда разработки IntelliJ IDEA, например в редакции Community или Giga IDE с установленным плагином Jmix.

Создадим новый проект выбрав Jmix в качестве типа и шаблон Full-Stack Application.

Система документооборота начинается с работы с файлами и каталогами.

Эти возможности уже реализованы в дополнении фреймворка Jmix под названием WebDAV. Это дополнение с коммерческой лицензией и вам потребуется приобрести Enterprise-подписку Jmix чтобы его использовать без ограничений. Она поставляется по открытой цене на каждого разработчика. Но если вы решили двигаться только на бесплатном функционале можно и обойтись возможностями community-версий фреймворка определив для любой сущности поле с типом FileRef для обеспечения возможности прикрепления к ним файлов. При этом техническая, т. е. не доступная обычным пользователям версионность может быть обеспечена возможностями подсистем Audit и подсистемы физического хранилища файлов. Как и любой фреймворк Jmix — это конструктор, позволяющий достигать результата различными способами.

В рамках данной работы мы продолжим с полнофункциональным комплектом ПО, выбрав и установив в маркетплейсе дополнений Jmix дополнение WebDAV.

Для работы с документами создадим новую сущность Document.

Включим для нее Trait’ы версионирования, аудита и мягкого удаления.

Версионирование — это поддержка историчности данных на уровне приложения, благодаря ей вы можете поднять историю изменений каждой записи и откатиться к предыдущим значениям. Все это “прозрачно”, т. е. без явного усложнения реализации на прикладном уровне и собственных изобретений.

Аудит - хранит данные об авторстве изменений и времени, в которое они произошли.

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

Для документов нам понадобится поле с заголовком и возможность прикрепить файл документа. Все это позволит нам сделать дизайнер сущностей Jmix Studio.

Для заголовка подойдет стандартное текстовое поле.

Файл мы добавим в виде ассоциативной связи One-To-One нашей сущности Document с сущностью WebdavDocument, которую предоставляет дополнение WebDAV.

Когда сущности готовы мы можем сгенерировать для них экраны управления данными.

Разграничиваем доступы

Jmix как технология корпоративной разработки поставляется с включенной по умолчанию развитой подсистемой безопасности. Впрочем, ее использование может показаться проще настройки собственных конфигураций Spring Security.

Возможности разграничения доступа позволяют определять как ресурсные роли (Resource Role), которые напоминают классические реализации RBAC и определяют доступы к сущностям, их свойствам, пунктам меню и экранам на основе принадлежностей пользователя к перечню ролей.

Дополнительно вы можете определять роли уровня строк (Row-level Role), которые позволяют управлять доступом к отдельным записям таблиц базы данных используя условия относительно контекста текущей авторизации, т.е. реализует атрибутивную модель доступа(ABAC), которая оказывается востребованной в инфраструктурах с повышенными требованиями к безопасности.

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

Для управления собственными документами создадим ресурсную роль с названием DocumentManagementRole в которой разрешим доступ к сущностям аддона WebDAV и их свойствам и экранам, а также к созданной нами ранее сущности Document. Также роль должна открывать доступ к пункту меню интерфейса модели Document и ее экранам, а также экранам WebDAV и списка пользователей. При этом возможность изменять самих пользователей следует ограничить, оставив доступ только на чтение.

Для упрощения задачи можно пользоваться вкладками визуального редактирования разрешений. В первую очередь нам будут интересны ”User Interface” и ”Entities”. Первая позволяет определить доступы к экранам и пунктам меню, а вторая к сущностям и их атрибутам.

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

Также нам необходимо ограничить доступ к WebdavDocument, разрешая рядовым пользователям управлять только своими файлами. Для этого создадим Row-level роль ManageOwnWebdavDocuments и добавим в нее JPQL Policy с выражением "{E}.createdBy = :current_user_username" реализующим требуемое ограничение.

public interface ManageOwnWebdavDocumentsRole { 
    String CODE = "manage-own-webdav-documents"; 
 
    @JpqlRowLevelPolicy(entityClass = WebdavDocument.class, where = "{E}.createdBy = :current_user_username") 
    void webdavDocument(); 
} 

Чтобы убедиться, что все работает, запустим наше приложение.

Откроем во вкладке инкогнито браузера адрес http://localhost:8080 и авторизуемся логином и паролем для разработки admin/admin.

В веб-интерфейсе управления пользователями создадим пользователей user и user2 с ролями DocumentManagementRole, ManageOwnWebdavDocuments, а также UI: minimal access и WebDAV: minimal access.

UI: minimal access - поставляемая шаблоном приложения роль с минимально достаточным набором разрешениям для использования веб-интерфейса.

WebDAV: minimal access - базовые разрешения для работы с сущностями и интерфейсом расширения

Для более гибкого управления доступом добавим модели Document ассоциативные связи типа Many-To-Many к модели User в полях readers и writers.

Сгенерируем элементы форм в экранах управления при помощи специальной кнопки “Add to Views”.

Чтобы подсистема безопасности разрешала доступы по спискам на чтение и запись, создадим соответствующие Row-level роли.

@RowLevelRole(name = "ReadSharedDocumentRole", code = ReadSharedDocumentRole.CODE) 
public interface ReadSharedDocumentRole { 
    String CODE = "read-shared-document-role"; 
 
    @PredicateRowLevelPolicy( 
            entityClass = Document.class, 
            actions = {RowLevelPolicyAction.READ}) 
    default RowLevelBiPredicate<Document, ApplicationContext> documentReaders() { 
        return (document, applicationContext) -> { 
            CurrentAuthentication currentAuthentication = applicationContext.getBean(CurrentAuthentication.class); 
            return document.getCreatedBy().equals(currentAuthentication.getUser().getUsername()) || (document.getReaders() != null 
                    && document.getReaders().stream().anyMatch((u) -> u.getUsername().equals(currentAuthentication.getUser().getUsername()))); 
        }; 
    } 
} 

@RowLevelRole(name = "WriteSharedDocumentRole", code = WriteSharedDocumentRole.CODE) 
public interface WriteSharedDocumentRole { 
    String CODE = "write-shared-document-role"; 
 
    @PredicateRowLevelPolicy( 
            entityClass = Document.class, 
            actions = {RowLevelPolicyAction.UPDATE}) 
    default RowLevelBiPredicate<Document, ApplicationContext> documentWriters() { 
        return (document, applicationContext) -> { 
            CurrentAuthentication currentAuthentication = applicationContext.getBean(CurrentAuthentication.class); 
            return document.getCreatedBy().equals(currentAuthentication.getUser().getUsername()) || (document.getWriters() != null 
                    && document.getWriters().stream().anyMatch((u) -> u.getUsername().equals(currentAuthentication.getUser().getUsername()))); 
        }; 
    } 
} 

Назначим эти роли, тестовым пользователям. Результат должен выглядеть вот так:

Авторизуемся под одним из них, создадим документы с упоминанием второго пользователя среди имеющих право только на чтение и в обоих списках. Затем авторизуемся под вторым и убедимся, что он видит оба созданных документа, но менять может только тот, в котором назначен в список имеющих право на изменение.

В перспективе, при помощи Jmix-дополнений LDAP и OpenID Connect модель безопасности приложения может быть легко интегрирована в корпоративные системы аутентификации.

Подключаем хранилище файлов S3 от Облакотеки

Для хранения файлов документов мы будем использовать хостинг S3. В России многие популярные хостинги перестали работать, но S3 стал своего рода стандартным протоколом, под который сторонние хостеры предлагают совместимые решения. Одним из таких является Облакотека.

S3 Хранилище — это отказоустойчивое масштабируемое решение. Если что-то произойдёт с серверами приложения наработанные файлы не пропадут, т. к. данные реплицируются на нескольких серверах, которые даже при отказе значительной части из них продолжат работу по обслуживанию запросов пользователей.

Для использования хранилища файлов S3 в Jmix необходимо установить аддон AWS File Storage. При этом плагин Local File Storage должен быть удален, либо настроено совместное использование подсистем хранения.

Это можно добавив в build.gradle зависимость:

implementation 'io.jmix.awsfs:jmix-awsfs-starter'

И отключить реализацию, использующую локальную файловую систему:

//implementation 'io.jmix.localfs:jmix-localfs-starter'

Для начала работы с сервисом создадим новое хранилище в соответствующем разделе:

и в нем теплый бакет и ключи доступа.

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

После того как все создалось, нам остаётся только прописать полученные реквизиты в application.properties и перезапустить сервер.

jmix.awsfs.secret-access-key = {ключ} 
jmix.awsfs.region = ee-east 
jmix.awsfs.bucket = {имя_бакета} 
jmix.awsfs.endpoint-url = https://storage.oblakoteka.ru:443 

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

Подготавливаем проект к развертыванию

В рамках данной статьи мы развернем базовый вариант приложения на виртуальной машине используя Docker Compose.

Для конфигурации приложения на сервере создадим продуктивную конфигурацию приложения в файле application-prod.properties, а для сохранения возможности запускать приложения локально на компьютерах разработчиков вся локальная специфика будет храниться в файле application-dev.properties. В нем будет:

main.datasource.username = sa 
main.datasource.password = 

Тогда как продуктивная конфигурация будет получать все параметры из переменных окружения:

main.datasource.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}?stringtype=unspecified 
main.datasource.username=${DB_USER} 
main.datasource.password=${DB_PASSWORD} 
 
jmix.awsfs.access-key=${S3_KEY} 
jmix.awsfs.secret-access-key=${S3_SECRET_KEY} 
jmix.awsfs.region=${S3_REGION} 
jmix.awsfs.bucket=${S3_BUCKET} 
jmix.awsfs.endpoint-url=${S3_ENDPOINT_URL} 

jmix.core.temp-dir=/tmp 

jmix.webdav.url-prefix=http://${SITE_HOST}:8080/webdav 

В build.gradle добавляем включение соответствующего профиля

bootRun { 
    args = ["--spring.profiles.active=dev"] 
} 
 
tasks.named("bootBuildImage") { 
    environment["BPE_APPEND_JAVA_TOOL_OPTIONS"] = " -Dspring.profiles.active=prod" 
} 

А также драйвер для СУБД PostgreSQL в перечень зависимостей:

implementation 'org.postgresql:postgresql'

Таким образом, для локальной разработки и продуктивной среды будут выбраны подходящие инфраструктурные настройки.

Теперь мы можем собрать Docker-образ командой:

./gradlew -Pvaadin.productionMode=true bootBuildImage ,

которая использует инструменты создания образов Spring Boot.

И упаковать в архив для загрузки на сервер:

docker save docker.io/library/sm-dms:0.0.1-SNAPSHOT | gzip > sm-dms.tar.gz

Разворачиваем приложение на сервере

Для развертывания приложения в панели управления Облакотеки создадим виртуальную машину KVM.

Диска лучше прибавить и количество процессорных ядер хотя бы до 4-х.

Чтобы подключиться к серверу по SSH надо выполнить проброс порта 2222 на внутренний 22 по NAT примерно так же, как мы это делаем на домашнем роутере:

Зайдем на сервер по SSH с учетными данными, которые можно увидеть в списке виртуальных машин под кнопкой “Данные администратора ВМ”.

Также на этом экране можно узнать выданный вам внешний ip-адрес, у меня он 37.18.77.154 и далее в примерах кода и команд вам нужно будет иметь в виду свой собственный вместо него.

Также можно использовать более необычный внешний порт вместо 2222, это поможет уклониться от большинства автоматизированных взломщиков.

Чтобы можно было подключаться к серверу, не вводя каждый раз пароль, сначала добавим открытую часть SSH ключа на сервер:

ssh-copy-id -i ~/.ssh/id_ root@37.18.77.154 -p 2222

Присоединившись при помощи команды:

ssh root@37.18.77.154 -p 2222

Установим на сервере пакеты для Docker’а по официальной инструкции отсюда: https://docs.docker.com/engine/install/

Для сервера на Ubuntu это вылилось в такие команды:

sudo apt-get update 
sudo apt-get install ca-certificates curl 
sudo install -m 0755 -d /etc/apt/keyrings 
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc 
sudo chmod a+r /etc/apt/keyrings/docker.asc 
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 

В другом терминальном сеансе загрузим архив с Docker-образом на сервер при помощи команды scp:

scp -P2222 sm-dms.tar.gz root@37.18.77.154:/tmp

И на сервере загружаем образ:

gunzip -c /tmp/sm-dms.tar.gz | docker load

Для конфигурации запуска Docker-образов при помощи Docker Compose на сервере создадим каталог:

mkdir -p ~/projects/smdms

И в нем файл docker-compose.yml

services: 
  smdms-postgres: 
    container_name: smdms-postgres 
    image: postgres:latest 
    ports: 
      - "5432:5432" 
    volumes: 
      - postgres:/var/lib/postgresql/data 
    environment: 
      - POSTGRES_USER=smdms 
      - POSTGRES_PASSWORD=QbULNuKtm15 
      - POSTGRES_DB=smdms 
  smdms: 
    container_name: smdms 
    image: sm-dms:0.0.1-SNAPSHOT 
    ports: 
      - "8080:8080" 
    environment: 
      - SITE_HOST=37.18.77.154 
      - DB_HOST=smdms-postgres 
      - DB_USER=smdms 
      - DB_PASSWORD=QbULNuKtm15 
      - DB_PORT=5432 
      - DB_NAME=smdms 
      - S3_KEY=5OUCXXXXXXXXXKZU 
      - S3_SECRET_KEY=g2yTpLxxxxxxKhWguXIBJUFIUPy3ZNOz 
      - S3_REGION=ee-east 
      - S3_BUCKET=smdmsdocs 
      - S3_ENDPOINT_URL=https://storage.oblakoteka.ru:443 
    depends_on: 
      - smdms-postgres 
volumes: 
  postgres: {} 

Пароли и секреты надо заменить на свои безопасные конечно же.

И запускаем:

docker compose -f docker-compose.yml up –d

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

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

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

Создадим в проекте на сервере подкаталог db_ssl и распакуем туда ключи. Затем надо перейти в этот каталог и конвертировать ключ клиента в формат DER.

openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in client-key.pem -out client-key.der

Должна получиться такая картина:

В файле приложения application-prod.properties мы вынесем параметры подключения в переменную, чтобы получилось вот так:

main.datasource.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}?${DB_PARAMS}

Пересоберем и зальем Docker-образ на сервер.

В docker-compose.yml добавим переменную параметров подключения

- DB_PARAMS=stringtype=unspecified&sslmode=prefer&sslrootcert=/tmp/db_ssl/ca-cert.pem&sslcert=/tmp/db_ssl/client-cert.pem&sslkey=/tmp/db_ssl/client-key.der

И прокинем подкаталог db_ssl в контейнер, добавив подсекцию volumes в конфигурацию приложения smdms:

    volumes: 

      - ./db_ssl:/tmp/db_ssl 

После этого остается только перезапустить контейнер и наблюдать логи.

  docker compose -f docker-compose.yml up --force-recreate -d 

  docker logs smdms 

Следует учитывать, что в этом случае база будет развернута “с нуля” и в нее попадет только схема данных и те данные, для которых вы создали миграционные скрипты самостоятельно. Если при работе с сущностями скрипты за вас создаст среда разработки Jmix Studio, то свои собственные легко создать при помощи меню из структурного отображения проекта.

Скрипты liquibase создаются по умолчанию в xml-формате и позволяют вести их в независимом от реализации сервера баз данных виде.

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

После того как сервер успешно запустился, чтобы увидеть результат, нам надо прокинуть порт 8080 так же, как мы это делали, конфигурируя SSH. И затем открыть из браузера адрес: http://37.18.77.154:8080.

После успешного входа под пользователем admin на сервере, не забудьте поменять ему пароль на более сложный.

Онлайн-редактирование

Аддон WebDAV дает еще одну интересную возможность - это открытие документов для непосредственного редактирования в офисных пакетах, причем поддерживаются как Microsoft Office так и LibreOffice и потенциально любой офисный пакет умеющий работать по одноименному протоколу.

Для корректной работы по HTTPS необходимо настроить прокси-сервер или самое приложение для работы с актуальными SSL-сертификатами. Сейчас для РФ это означает выпуск сертификата вручную или его покупку средствами доменного регистратора. Также возможна работа на самоподписанных сертификатах, что может ограничить работоспособность онлайн-редактирования в отдельных офисных пакетах или с выпуском собственного корневого сертификата, который надо будет устанавливать пользователям, работающим с системой.

Инструкция по настройке есть в документации: https://docs.jmix.io/jmix/webdav/configuring-https.html

Из специфичного для аддона WebDAV следует иметь в виду, что для корректного открытия документов для онлайн-редактирования в application.properties необходимо указать параметр, указывающий на доменное имя или ip-адрес нашего приложения:

jmix.webdav.url-prefix=https://37.18.77.154:8443/webdav

Номер порта(8080) - опциональный параметр, на случай если он у вас отличается от стандартных 80/443

Вместо ip-адреса будет доменное имя если вы заказали SSL-сертификаты. И лучшим решением будет добавить еще переменную для протокола, определив ее значение в docker-compose.yml

jmix.webdav.url-prefix=${SITE_PROTO}://${SITE_HOST}/webdav

Использование BPM

Мало какая система документооборота сейчас обходится без использования движка бизнес-процессов. Для Jmix существует дополнение позволяющее разрабатывать и интегрировать схемы созданные по открытому стандарту BPMN в свое приложение и делать это не выходя за пределы инфраструктуры BPMN 2.0, т.е. обходясь только средой разработки и веб-интерфейсом подсистемы.

Интеграция со сторонней CRM

Для интеграции со сторонними сервисами можно предположить два варианта:

  1. Вы разрабатываете REST API для обмена данными и дорабатываете интеграционное решение для работы с ним. Для этого в Jmix есть дополнение Generic Rest (REST API), которое автоматически отображает сущности и репозитории в сервисные интерфейсы, документированные в OpenAPI. А также оно позволяет добавлять собственные эндпоинты.

  2. Вы разрабатываете функциональность обмена данными на стороне своего приложения. Чтобы регулярно запускать процедуры обмена данными может пригодиться Jmix-дополнение Quartz, которое представляет собой интерфейс к известному планировщику и позволяя создавать собственные классы Job’ов и управлять их задачами как программно, так и из веб-интерфейса.

Таким образом, в рамках данной статьи мы создали и развернули корпоративное веб-приложение для совместной работы с документами на реальном сервере. Наше приложение позволяет пользователям работать с документами совестно с разграничением доступа и сохранением исторических данных. Все данные резервируются и их можно восстановить и перенести на другие серверы, а приложение может быть оперативно развернуто заново при необходимости. Также мы рассмотрели пути развития приложения для реализации функций коробочных версий корпоративных систем. Эти задачи будут продуктивнее решены разработчиками с дополнениями платформы Jmix, расширениями Vaadin и Spring Boot, которые они смогут использовать вместе в рамках одного проекта при разработке. Это позволит им фокусироваться на решении бизнес-задач вместо изобретения “велосипедов”, быстрее и дешевле достигать более качественных результатов.

Jmix - это open-source платформа быстрой разработки бизнес-приложений на Java