EN · DE · RU · FR · ES

#238: MerlinExecutionPageRest.kt

plugins/org.projectforge.plugins.merlin/src/main/kotlin/org/projectforge/plugins/merlin/rest/MerlinExecutionPageRest.kt Тип: Kotlin · Роль: Регистрация плагина · Источник: plugins/org.projectforge.plugins.merlin/src/main/kotlin/org/projectforge/plugins/merlin/rest/MerlinExecutionPageRest.kt 415 строк · 355 кода · 34 комментария · 26 пустых
REST API конечная точка для MerlinExecution. Обрабатывает HTTP-запросы и возвращает JSON-ответы для React-фронтенда.

Структура кода

Аннотации: Valid, RequestBody, GetMapping, PathVariable, Autowired, return, RequestMapping, RestController, PostMapping, RequestParam

Классы: MerlinExecutionPageRest

Супертип(ы): AbstractDynamicPageRest

Функции (12): execute, serialExecution, validate, downloadSerialExecutionTemplate, getForm, createSerialExcelDownloadMenu, createInputElement, hasEditAccess, getUserPref, getFieldId, addUsers, registerColumn

Свойства (51): merlinTemplateDao, merlinHandler, merlinRunner, merlinPagesRest, userPrefService, userService, employeeService, executionData, userPref, errors, filename, wordBytes, download, pdfResult, filename, zipFilename, zipByteArray, validationErrors, stats, inputVariables, inputData, filename, excel, logViewerMenuItem, id...

Импорты: 30 пакетов

Пакет: org.projectforge.plugins.merlin.rest

Исходный код (сокращён)

package org.projectforge.plugins.merlin.rest

import de.micromata.merlin.excel.ExcelSheet
import de.micromata.merlin.word.templating.VariableType
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import mu.KotlinLogging
import org.projectforge.business.fibu.EmployeeService
import org.projectforge.business.user.UserGroupCache
import org.projectforge.business.user.service.UserPrefService
import org.projectforge.business.user.service.UserService
import org.projectforge.common.FormatterUtils
import org.projectforge.framework.i18n.translate
import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext
import org.projectforge.framework.utils.NumberHelper
import org.projectforge.menu.MenuItem
import org.projectforge.menu.MenuItemTargetType
import org.projectforge.plugins.merlin.*
import org.projectforge.rest.admin.LogViewerPageRest
import org.projectforge.rest.config.Rest
import org.projectforge.rest.config.RestUtils
import org.projectforge.rest.core.AbstractDynamicPageRest
import org.projectforge.rest.core.PagesResolver
import org.projectforge.rest.core.RestResolver
import org.projectforge.rest.dto.FormLayoutData
import org.projectforge.rest.dto.PostData
import org.projectforge.ui.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile

private val log = KotlinLogging.logger {}

@RestController
@RequestMapping("${Rest.URL}/merlinexecution")
class MerlinExecutionPageRest : AbstractDynamicPageRest() {

    @Autowired
    private lateinit var merlinTemplateDao: MerlinTemplateDao

    @Autowired
    private lateinit var merlinHandler: MerlinHandler

    @Autowired
    private lateinit var merlinRunner: MerlinRunner

    @Autowired
    private lateinit var merlinPagesRest: MerlinPagesRest

    @Autowired
    private lateinit var userPrefService: UserPrefService

    @Autowired
    private lateinit var userService: UserService

    @Autowired
    private lateinit var employeeService: EmployeeService

    /**
     * Будет вызван, если пользователь хочет изменить свой observeStatus.
     */
    @PostMapping("execute")
    fun execute(@Valid @RequestBody postData: PostData<MerlinExecutionData>): ResponseEntity<*> {
        MerlinPlugin.ensureUserLogSubscription()
        val executionData = postData.data
        log.info("Пользователь хочет выполнить '${executionData.name}'...")
        // Сохранить введённые значения как пользовательские настройки:
        val userPref = getUserPref(executionData.id)
        userPref.inputVariables = executionData.inputVariables
        userPref.pdfFormat = executionData.pdfFormat
        val errors = validate(executionData)
        if (!errors.isNullOrEmpty()) {
            return ResponseEntity(ResponseAction(validationErrors = errors), HttpStatus.NOT_ACCEPTABLE)
        }
        val result = merlinRunner.executeTemplate(executionData.id, executionData.inputVariables)
        var filename = result.first
        val wordBytes = result.second
        var download = wordBytes
        if (executionData.pdfFormat) {
            try {
                val pdfResult = merlinRunner.convertToPdf(wordBytes, filename)
                filename = pdfResult.filename
                download = pdfResult.content
            } catch (ex: Throwable) {
                // Может возникнуть переполнение стека.
                log.error("Ошибка при конвертации в pdf (возврат к docx): ${ex.message}", ex)
            }
        }
        return RestUtils.downloadFile(filename, download)
    }

    @PostMapping("serialExecution/{id}")
    fun serialExecution(
        @PathVariable("id", required = true) id: Long,
        @RequestParam("file") file: MultipartFile
    ): ResponseEntity<*> {
        MerlinPlugin.ensureUserLogSubscription()
        val filename = file.originalFilename
        log.info {
            "Пользователь пытается загрузить файл последовательного выполнения: id='$id', имя файла='$filename', размер=${
                FormatterUtils.formatBytes(
                    file.size
                )
            }."
        }
        val result = file.inputStream.use { inputStream ->
            merlinRunner.serialExecuteTemplate(id, filename ?: "untitled.xlsx", inputStream)
                ?: throw IllegalArgumentException("Не удалось выполнить последовательный Excel-файл.")
        }
        val zipFilename = result.first
        val zipByteArray = result.second
        return RestUtils.downloadFile(zipFilename, zipByteArray)
    }

    private fun validate(data: MerlinExecutionData): List<ValidationError>? {
        val validationErrors = mutableListOf<ValidationError>()
        val stats = merlinHandler.analyze(data.id).statistics
// ... (сокращено, всего 393 строки)

История Git

868d6abb7 2025 -> 2026
420ce7f24 Merlin: исправление экспорта списка сотрудников для последовательной функции.
bf998d85f BankingServicesRest, DataTransferPublicServicesRest, MerlinExecutionPageRest, ButlerServicet: stream.use (потоки не были закрыты ранее?)
63081666f Заголовки исходных файлов: 2024 -> 2025.
e80642c81 Работа над миграцией в процессе... (все тесты всех пакетов: OK).