Дата анализа: 2026-04-17
Ветка (на момент анализа): release/3.3.0-SNAPSHOT
Текущая версия в протоколе: 3.4.0-SNAPSHOT (локальный репоobesrabuo-common-lib/)
Контекст: Анализ перед рефакторингом protocol-service. Фиксы библиотеки — prerequisite для pipeline генерации.Статусы применения (колонка Status в таблице "Порядок фиксов"):
Applied 3.4.0— фикс внедрён в версии 3.4.0-SNAPSHOTPending (service)— фикс на стороне protocol-service, не в common-libVerify— статус неясен, нужно свериться с исходникамиobesrabuo-common-lib/перед первым использованиемPending— не внедрено, план остаётся
Два модуля common-lib критичны для protocol-service: docx4j-starter (генерация документов) и common-filestorage (ECM хранилище). Оба имеют системную проблему: ошибки глотаются молча, возвращая пустые объекты или "красный текст" вместо пробрасывания исключений. Это нарушает принцип "всё или ничего" и приводит к невалидным документам на проде.
StamperDocumentEngine.java (712 LOC) — движок генерацииDocumentInsertProcessor.java (828 LOC) — вставка вложенных документовStyleMergeHelper.java (83 LOC) — мерж стилейNumberingMergeHelper.java (152 LOC) — мерж нумерацииПроблема: insertDocument() ловит ВСЕ исключения и заменяет контент на красный текст [ОШИБКА: ...]. Caller не знает об ошибке. Протокол формируется с дырками.
Все точки молчаливого проглатывания:
| Строка | Условие | Текущее поведение | Должно быть |
|---|---|---|---|
| 82-86 | path == null или пустой | removeCommentedElements() молча |
Бросить DocumentInsertException("path is null") |
| 132-135 | parent не ContentAccessor | replaceWithError() |
Бросить исключение |
| 150-152 | позиция не найдена | replaceWithError() |
Бросить исключение |
| 243-246 | любое Exception | replaceWithError() |
Пробросить, обернув в DocumentInsertException |
Фикс:
DocumentInsertException extends RuntimeExceptionreplaceWithError() полностьюDocumentGenerationErrorРиск: Если другие сервисы (agenda) используют insertDocument и полагаются на текущее поведение (красный текст вместо падения) — нужно проверить. Вероятность низкая — красный текст в документе никому не нужен.
Проблема: В pipeline генерации протокола шрифты нормализуются дважды:
generate(path, model) → normalizeFonts=true (default)Для 100 вопросов — 200 обходов DOM вместо 100.
Фикс: API generate(path, model, false) уже существует. В protocol-service:
generate(path, model, false) — без нормализацииФайл: Изменений в common-lib не нужно. Фикс на стороне protocol-service.
Проблема: При ошибке генерации — stacktrace из DocumentEngine без информации какой шаблон, какие данные.
Фикс: Обогатить исключение контекстом:
} catch (Exception e) {
throw new DocumentServiceException(
"Generation failed for template: " + templatePath, e);
}
Файл: StamperDocumentEngine.java:173
Проблема: ConcurrentHashMap<String, byte[]> растёт бесконечно.
Оценка: На практике ~26 шаблонов × ~200KB = ~5MB. Не критично.
Фикс:
public static void clearCache() для тестовПроблема: DocumentInsertProcessor:307-310 — ошибка копирования image/OLE отношений логируется на DEBUG и пропускается.
Фикс: Поднять до WARN. Пропущенная картинка — видимый дефект документа.
EcmIOResourceAccess.java — ECM клиент (HTTP)MinioIOResourceAccess.java — S3 клиент (Minio)EcmAccessException.java — исключение ECMEcmClientProperties.java — конфигурацияПроблема: EcmIOResourceAccess:343-388 — при ошибке upload (null response, error status, null body) возвращается пустой FileInfoDTO.builder().build(). FileService получает пустой UUID → сохраняет entity с null externalStorageKey → последующая загрузка упадёт.
Текущее:
if (responseEntity == null) {
log.error("Response entity is null for file {}", fileName);
return FileInfoDTO.builder().build(); // пустой DTO!
}
Фикс: Бросать EcmAccessException при любой ошибке upload:
if (responseEntity == null) {
throw new EcmAccessException(500, fileName, "ECM returned null response");
}
Проблема: FileService.java:453-469 — при ошибке загрузки файла из ECM файл пропускается, мерж продолжается. Протокол формируется без вопроса.
Фикс: Fail entire merge если хоть один файл не загружен:
// Вместо try-catch с continue
// Пусть исключение пролетит наверх → весь протокол не создаётся
Проблема: isRetryableStatusCode() включает 404. Если файл не существует — 3 retry с exponential backoff = ~14 секунд задержка. Файл от этого не появится.
Фикс: Убрать 404 из retryable. Fail fast для "не найден":
private boolean isRetryableStatusCode(int status) {
return status == 429 || status >= 500; // без 404
}
Проблема: EcmIOResourceAccess:133-143 — HTTP 200 с пустым body, после 3 retry → возвращает Optional.empty() вместо ошибки. Caller интерпретирует как "файл не найден" — вводящее в заблуждение.
Фикс: После исчерпания retry бросать исключение:
throw new EcmAccessException(200, fileId,
"ECM returned empty body after " + maxRetries + " retries");
Проблема: deleteFile() логирует ошибку но не бросает. deleteFiles() возвращает boolean, caller может не проверить.
Фикс:
deleteFile() → бросать EcmAccessException при ошибкеПроблема: stream.readAllBytes() для больших файлов без timeout. Если сеть зависла после headers — бесконечное ожидание.
Фикс: Ограничить через InputStream.readNBytes(maxSize) или обернуть в timeout:
byte[] data = stream.readNBytes(MAX_FILE_SIZE_BYTES);
if (stream.read() != -1) {
throw new EcmAccessException(413, fileId, "File exceeds maximum size");
}
Проблема: deleteFiles() при 404 логирует "There is a deleted file in the batch" — не говорит какой именно.
Фикс: Логировать конкретные fileId. Или перейти на поштучное удаление с агрегацией результатов.
Проблема: Только 2 параллельных запроса к ECM. При генерации протокола с 50 вопросами — bottleneck.
Оценка: Нужно проверить лимиты ECM-сервера. Возможно можно увеличить.
Фикс: Сделать конфигурируемым через properties (уже есть maxConcurrentRequests, но код на строке 52 хардкодит 2).
| Элемент | Почему |
|---|---|
| MinioIOResourceAccess | Используется как fallback/dev, не в проде |
| Template caching algorithm | Работает, 26 шаблонов — не проблема |
| Font normalization algorithm (Arial 10pt) | Бизнес-требование, не баг |
| NumberingMergeHelper / StyleMergeHelper | Работают корректно, проблем не выявлено |
| office-stamper integration | Стабильна после миграции с Aspose |
| Приоритет | ID | Модуль | Сложность | Риск | Status |
|---|---|---|---|---|---|
| 1 | C-1 | docx4j | Средняя | Высокий (проверить всех потребителей) | Applied 3.4.0 |
| 2 | ECM-C1 | filestorage | Низкая | Средний | Applied 3.4.0 (Resilience4j) |
| 3 | ECM-C2 | filestorage | Низкая | Низкий (фикс в protocol-service) | Pending (service) |
| 4 | ECM-H1 | filestorage | Низкая | Низкий | Applied 3.4.0 (Resilience4j) |
| 5 | ECM-H2 | filestorage | Низкая | Низкий | Applied 3.4.0 (Resilience4j) |
| 6 | H-2 | docx4j | Низкая | Низкий | Verify |
| 7 | ECM-H3 | filestorage | Низкая | Средний | Verify |
| 8 | TMP | docx4j | Средняя | Низкий (новый класс) | Verify |
| 9 | M-1 | docx4j | Низкая | Нет | Verify |
| 10 | ECM-M1-M3 | filestorage | Низкая | Низкий | Verify |
| 11 | H-1 | protocol-service side | Низкая | Нет | Pending (service) |
.m2