pcp-tgu-service: миграция с WebFlux на servlet/MVC
WebFlux в tgu был фиктивным: блокирующая JPA, контроллеры оборачивали
блокирующие вызовы в Mono.fromCallable{}.subscribeOn(boundedElastic()),
WebClient .block()-ался сразу. Единственный WebFlux-only сервис флота.
- build: подключён pcp-types-lib (тянет starter-web → servlet), springdoc
webflux-ui → webmvc-ui; starter-webflux оставлен только ради WebClient
- контроллеры возвращают значения напрямую (без Mono-обёрток)
- удалено локальное Jackson-2 зеркало PcpTimeModule.kt → модуль из либы
в CamundaJacksonConfig; Jackson-3 зеркало остаётся (Boot 4 держит
Jackson 3 на HTTP-границе и для MVC), WebFluxJacksonConfig → RestJacksonConfig
This commit is contained in:
@@ -65,17 +65,16 @@
|
|||||||
`findAndRegisterModules()` (напр. `pcp-request-service`).
|
`findAndRegisterModules()` (напр. `pcp-request-service`).
|
||||||
- Точечная регистрация в мапперах, что обходят оба: `pcp-dynamic-plan-service/JacksonConfig`.
|
- Точечная регистрация в мапперах, что обходят оба: `pcp-dynamic-plan-service/JacksonConfig`.
|
||||||
|
|
||||||
**Особый случай — pcp-tgu-service.** Он не зависит от pcp-types-lib и работает на WebFlux
|
**Особый случай — pcp-tgu-service.** Сервис мигрирован с WebFlux на **servlet/MVC** (см. ниже
|
||||||
(подключение либы притянуло бы `spring-boot-starter-web`/MVC и переключило тип приложения на
|
«Миграция tgu на MVC») и теперь зависит от pcp-types-lib, как остальной флот. Но из-за Boot 4 на
|
||||||
сервлетный). Поэтому в tgu лежат **два локальных зеркала** (поведение обязано совпадать с
|
HTTP-границе всё равно работает **Jackson 3**, поэтому время покрывается двумя путями:
|
||||||
библиотечным, правки синхронизировать), покрывающих обе Jackson-среды сервиса:
|
- Jackson-2 модуль **из либы** (`pcp_types_lib.utils.json.PcpTimeModule`) — явно зарегистрирован в
|
||||||
- `config/PcpTimeModule.kt` (Jackson 2) — зарегистрирован в `CamundaJacksonConfig`, покрывает
|
ручном маппере `CamundaJacksonConfig` (Camunda/внутренний парсинг); своего зеркала больше нет.
|
||||||
Camunda/внутренний парсинг;
|
- `config/PcpTimeModuleV3.kt` (**Jackson 3**, `tools.jackson.*`) — REST HTTP message converters (MVC),
|
||||||
- `config/PcpTimeModuleV3.kt` (**Jackson 3**, `tools.jackson.*`) — REST-кодеки WebFlux, которые
|
которые Boot 4 авто-конфигурирует на Jackson 3 `JsonMapper`. Регистрируется бином
|
||||||
Boot 4 авто-конфигурирует на Jackson 3 `JsonMapper`. Регистрируется бином
|
`tools.jackson.databind.JacksonModule` (`RestJacksonConfig`); `JacksonAutoConfiguration` собирает
|
||||||
`tools.jackson.databind.JacksonModule` (`WebFluxJacksonConfig`); `JacksonAutoConfiguration`
|
все такие бины в общий `JsonMapper` (`builder.addModules(...)`). Лежит локально, т.к. либа даёт
|
||||||
собирает все такие бины в общий `JsonMapper` (`builder.addModules(...)`). Тем самым входящий
|
только Jackson-2 модуль; поведение обязано совпадать с библиотечным, правки синхронизировать.
|
||||||
REST-JSON tgu тоже читается толерантно к обоим форматам.
|
|
||||||
|
|
||||||
Контракт слоя закреплён `PcpTimeModuleTest` (pcp-types-lib): оба формата → один и тот же UTC-момент,
|
Контракт слоя закреплён `PcpTimeModuleTest` (pcp-types-lib): оба формата → один и тот же UTC-момент,
|
||||||
плюс проверка авто-подхвата через SPI.
|
плюс проверка авто-подхвата через SPI.
|
||||||
@@ -139,8 +138,8 @@ mission-planing) используют тот же дефолтный Jackson-к
|
|||||||
синхронно; фронт `parseUtc` совместим. Контракт закреплён тестами выше.
|
синхронно; фронт `parseUtc` совместим. Контракт закреплён тестами выше.
|
||||||
|
|
||||||
- **Слой толерантности (`PcpTimeModule`) — раскатан по флоту.** 12 сервисов на pcp-types-lib
|
- **Слой толерантности (`PcpTimeModule`) — раскатан по флоту.** 12 сервисов на pcp-types-lib
|
||||||
получают его авто-конфигурацией/SPI; dynamic-plan — точечно; tgu — двумя локальными зеркалами
|
получают его авто-конфигурацией/SPI; dynamic-plan — точечно; tgu — модулем из либы (Camunda,
|
||||||
(Jackson-2 для Camunda и Jackson-3 для REST-кодеков WebFlux). Это включатель: дальнейший флип
|
Jackson 2) плюс локальное Jackson-3 зеркало для REST-границы MVC. Это включатель: дальнейший флип
|
||||||
продюсеров на `Z` больше не требует синхронного деплоя.
|
продюсеров на `Z` больше не требует синхронного деплоя.
|
||||||
|
|
||||||
После толерантности продюсеров можно флипать **в любом порядке**. Кандидаты: остальные naive-DTO
|
После толерантности продюсеров можно флипать **в любом порядке**. Кандидаты: остальные naive-DTO
|
||||||
@@ -155,16 +154,25 @@ ui-service, ballistics, complex-mission, dynamic-plan, slots, coverage-scheme.
|
|||||||
`…+00:00`. **Инстант не изменился** (тот же момент), но стенные часы теперь совпадают с UTC, поэтому
|
`…+00:00`. **Инстант не изменился** (тот же момент), но стенные часы теперь совпадают с UTC, поэтому
|
||||||
потребитель, дропающий зону, получает корректное время. Закреплено `LocalDateTimeTimeZoneSerializerTest`.
|
потребитель, дропающий зону, получает корректное время. Закреплено `LocalDateTimeTimeZoneSerializerTest`.
|
||||||
|
|
||||||
### Исправленные баги формата
|
- **pcp-tgu-service REST-граница на Jackson 3** — теперь толерантна (см. 2bis). Добавлен Jackson-3
|
||||||
|
|
||||||
- **pcp-tgu-service REST-codec на Jackson 3** — теперь толерантен (см. 2bis). Добавлен Jackson-3
|
|
||||||
вариант модуля `config/PcpTimeModuleV3.kt` и его регистрация бином `JacksonModule`
|
вариант модуля `config/PcpTimeModuleV3.kt` и его регистрация бином `JacksonModule`
|
||||||
(`config/WebFluxJacksonConfig.kt`); входящий REST-JSON WebFlux читается в обоих форматах
|
(`config/RestJacksonConfig.kt`); входящий REST-JSON читается в обоих форматах (naive=UTC и `Z`).
|
||||||
(naive=UTC и `Z`). Закреплено `PcpTimeModuleV3Test`.
|
Закреплено `PcpTimeModuleV3Test`.
|
||||||
|
|
||||||
|
### Миграция tgu на MVC
|
||||||
|
|
||||||
|
- **pcp-tgu-service: WebFlux → servlet/MVC.** Сервис не имел реального реактивного кода — JPA
|
||||||
|
блокирующая, контроллеры оборачивали блокирующие вызовы в `Mono.fromCallable{}.subscribeOn(
|
||||||
|
boundedElastic())`, WebClient-вызовы `.block()`-ались сразу. Это был единственный WebFlux-only
|
||||||
|
сервис флота. Переведён на MVC: подключён pcp-types-lib (тянет `starter-web` → тип приложения
|
||||||
|
servlet), `springdoc` → `webmvc-ui`, контроллеры возвращают значения напрямую (без `Mono`).
|
||||||
|
`starter-webflux` оставлен только ради WebClient. Побочный эффект для времени: убрано локальное
|
||||||
|
Jackson-2 зеркало (`config/PcpTimeModule.kt`) в пользу модуля из либы; Jackson-3 зеркало остаётся
|
||||||
|
(Boot 4 держит Jackson 3 на HTTP-границе и для MVC).
|
||||||
|
|
||||||
### Остаток (под-задачи)
|
### Остаток (под-задачи)
|
||||||
|
|
||||||
- Обе Jackson-среды tgu теперь толерантны; отдельных остатков по чтению времени нет. При миграции
|
- Обе Jackson-среды tgu толерантны; отдельных остатков по чтению времени нет. При миграции
|
||||||
оставшихся продьюсеров на `Z` (раздел 4) добавлять сериализационные тесты контракта.
|
оставшихся продьюсеров на `Z` (раздел 4) добавлять сериализационные тесты контракта.
|
||||||
|
|
||||||
## 5. Правило для нового кода
|
## 5. Правило для нового кода
|
||||||
|
|||||||
@@ -17,11 +17,17 @@ java {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
// Общие типы/слой толерантности времени; тянет spring-boot-starter-web (servlet/MVC),
|
||||||
|
// из-за чего приложение запускается как сервлетное (MVC), а не WebFlux.
|
||||||
|
implementation(project(":libs:pcp-types-lib"))
|
||||||
|
|
||||||
// Spring Boot
|
// Spring Boot
|
||||||
|
// starter-webflux оставлен только ради WebClient (внешние HTTP-клиенты, см. integration/api);
|
||||||
|
// тип приложения — сервлетный (MVC), т.к. в classpath есть spring-webmvc из starter-web выше.
|
||||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
|
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
|
||||||
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:3.0.0")
|
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${property("versions.open-api")}")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-flyway")
|
implementation("org.springframework.boot:spring-boot-starter-flyway")
|
||||||
implementation("org.flywaydb:flyway-database-postgresql")
|
implementation("org.flywaydb:flyway-database-postgresql")
|
||||||
|
|||||||
+3
-2
@@ -10,6 +10,7 @@ import io.camunda.client.api.JsonMapper as CamundaJsonMapper
|
|||||||
import io.camunda.client.impl.CamundaObjectMapper
|
import io.camunda.client.impl.CamundaObjectMapper
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import space.nstart.pcp.pcp_types_lib.utils.json.PcpTimeModule
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class CamundaJacksonConfig {
|
class CamundaJacksonConfig {
|
||||||
@@ -25,8 +26,8 @@ class CamundaJacksonConfig {
|
|||||||
.addModule(KotlinModule.Builder().build())
|
.addModule(KotlinModule.Builder().build())
|
||||||
.addModule(JavaTimeModule())
|
.addModule(JavaTimeModule())
|
||||||
// Слой толерантности времени (читать naive=UTC и Z) — см. docs/standards/DATETIME_UTC.md.
|
// Слой толерантности времени (читать naive=UTC и Z) — см. docs/standards/DATETIME_UTC.md.
|
||||||
// Покрывает Jackson-2 путь (Camunda/внутренний парсинг). REST-codec на Jackson 3
|
// Модуль из pcp-types-lib; покрывает Jackson-2 путь (этот ручной маппер для Camunda).
|
||||||
// (WebFlux auto-config) покрыт близнецом PcpTimeModuleV3 (см. WebFluxJacksonConfig).
|
// REST-границу (Jackson 3) покрывает PcpTimeModuleV3 (см. RestJacksonConfig).
|
||||||
.addModule(PcpTimeModule())
|
.addModule(PcpTimeModule())
|
||||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
package space.nstart.pcp_tgu_service.config
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
import com.fasterxml.jackson.core.JsonToken
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeParseException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Локальное зеркало слоя толерантности времени из libs/pcp-types-lib
|
|
||||||
* (`space.nstart.pcp.pcp_types_lib.utils.json.PcpTimeModule`).
|
|
||||||
*
|
|
||||||
* Канонический источник — там; здесь копия, потому что pcp-tgu-service работает на WebFlux,
|
|
||||||
* а pcp-types-lib тянет `spring-boot-starter-web` (MVC/Tomcat) и переключил бы тип приложения
|
|
||||||
* на сервлетный. Поведение обязано совпадать с библиотечным; правки синхронизировать.
|
|
||||||
*
|
|
||||||
* Любой потребитель с этим модулем читает время в обоих форматах на проводе (naive=UTC и `Z`/офсет),
|
|
||||||
* нормализуя к UTC. Трогает только десериализацию. См. docs/standards/DATETIME_UTC.md.
|
|
||||||
*/
|
|
||||||
class PcpTimeModule : SimpleModule(NAME) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
addDeserializer(LocalDateTime::class.java, TolerantLocalDateTimeDeserializer())
|
|
||||||
addDeserializer(OffsetDateTime::class.java, TolerantOffsetDateTimeDeserializer())
|
|
||||||
addDeserializer(Instant::class.java, TolerantInstantDeserializer())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val NAME = "PcpTimeModule"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TolerantLocalDateTimeDeserializer : JsonDeserializer<LocalDateTime>() {
|
|
||||||
|
|
||||||
private val default = LocalDateTimeDeserializer.INSTANCE
|
|
||||||
|
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): LocalDateTime? {
|
|
||||||
if (p.currentToken() == JsonToken.VALUE_STRING) {
|
|
||||||
val text = p.text.trim()
|
|
||||||
if (text.isEmpty()) return null
|
|
||||||
try {
|
|
||||||
return OffsetDateTime.parse(text).withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
|
||||||
} catch (_: DateTimeParseException) {
|
|
||||||
// naive-строка без зоны — трактуем как UTC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return default.deserialize(p, ctxt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TolerantOffsetDateTimeDeserializer : JsonDeserializer<OffsetDateTime>() {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private val default = InstantDeserializer.OFFSET_DATE_TIME as JsonDeserializer<OffsetDateTime>
|
|
||||||
|
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): OffsetDateTime? {
|
|
||||||
if (p.currentToken() == JsonToken.VALUE_STRING) {
|
|
||||||
val text = p.text.trim()
|
|
||||||
if (text.isEmpty()) return null
|
|
||||||
try {
|
|
||||||
return OffsetDateTime.parse(text).withOffsetSameInstant(ZoneOffset.UTC)
|
|
||||||
} catch (_: DateTimeParseException) {
|
|
||||||
return LocalDateTime.parse(text).atOffset(ZoneOffset.UTC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return default.deserialize(p, ctxt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TolerantInstantDeserializer : JsonDeserializer<Instant>() {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private val default = InstantDeserializer.INSTANT as JsonDeserializer<Instant>
|
|
||||||
|
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Instant? {
|
|
||||||
if (p.currentToken() == JsonToken.VALUE_STRING) {
|
|
||||||
val text = p.text.trim()
|
|
||||||
if (text.isEmpty()) return null
|
|
||||||
try {
|
|
||||||
return OffsetDateTime.parse(text).toInstant()
|
|
||||||
} catch (_: DateTimeParseException) {
|
|
||||||
return LocalDateTime.parse(text).toInstant(ZoneOffset.UTC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return default.deserialize(p, ctxt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-5
@@ -16,11 +16,12 @@ import java.time.format.DateTimeParseException
|
|||||||
/**
|
/**
|
||||||
* Слой толерантности времени для **Jackson 3** (`tools.jackson.*`).
|
* Слой толерантности времени для **Jackson 3** (`tools.jackson.*`).
|
||||||
*
|
*
|
||||||
* Близнец [PcpTimeModule] (Jackson 2): то же поведение — читать время в обоих форматах на проводе
|
* Близнец библиотечного `PcpTimeModule` (Jackson 2): то же поведение — читать время в обоих
|
||||||
* (naive=UTC и `Z`/офсет), нормализуя к UTC; трогает только десериализацию. Нужен отдельно, потому
|
* форматах на проводе (naive=UTC и `Z`/офсет), нормализуя к UTC; трогает только десериализацию.
|
||||||
* что Boot 4 авто-конфигурирует Jackson 3 `JsonMapper` для REST-кодеков WebFlux, а он не видит
|
* Нужен отдельно, потому что Boot 4 авто-конфигурирует Jackson 3 `JsonMapper` для REST HTTP message
|
||||||
* Jackson-2 модули. Регистрируется как бин `tools.jackson.databind.JacksonModule`
|
* converters (MVC), а он не видит Jackson-2 модули. Регистрируется как бин
|
||||||
* ([WebFluxJacksonConfig]) — `JacksonAutoConfiguration` собирает такие бины в общий `JsonMapper`.
|
* `tools.jackson.databind.JacksonModule` ([RestJacksonConfig]) — `JacksonAutoConfiguration` собирает
|
||||||
|
* такие бины в общий `JsonMapper`.
|
||||||
*
|
*
|
||||||
* Канонический источник поведения — `space.nstart.pcp.pcp_types_lib.utils.json.PcpTimeModule`;
|
* Канонический источник поведения — `space.nstart.pcp.pcp_types_lib.utils.json.PcpTimeModule`;
|
||||||
* правки синхронизировать. См. docs/standards/DATETIME_UTC.md.
|
* правки синхронизировать. См. docs/standards/DATETIME_UTC.md.
|
||||||
|
|||||||
+4
-5
@@ -6,16 +6,15 @@ import tools.jackson.databind.JacksonModule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Регистрирует слой толерантности времени ([PcpTimeModuleV3]) в авто-конфигурируемом Jackson 3
|
* Регистрирует слой толерантности времени ([PcpTimeModuleV3]) в авто-конфигурируемом Jackson 3
|
||||||
* `JsonMapper`, который Boot 4 использует для REST-кодеков WebFlux.
|
* `JsonMapper`, который Boot 4 использует для REST HTTP message converters (MVC).
|
||||||
*
|
*
|
||||||
* `JacksonAutoConfiguration` собирает все бины `tools.jackson.databind.JacksonModule` из контекста
|
* `JacksonAutoConfiguration` собирает все бины `tools.jackson.databind.JacksonModule` из контекста
|
||||||
* и добавляет их в общий `JsonMapper` (`builder.addModules(...)`), поэтому достаточно объявить бин
|
* и добавляет их в общий `JsonMapper` (`builder.addModules(...)`), поэтому достаточно объявить бин
|
||||||
* этого типа. Закрывает tgu-follow-up из docs/standards/DATETIME_UTC.md: входящий REST-JSON теперь
|
* этого типа. Входящий REST-JSON читается толерантно к обоим форматам времени (naive=UTC и `Z`),
|
||||||
* читается толерантно к обоим форматам времени (naive=UTC и `Z`), как и Jackson-2 путь
|
* как и Jackson-2 путь Camunda ([CamundaJacksonConfig]). См. docs/standards/DATETIME_UTC.md.
|
||||||
* ([CamundaJacksonConfig]).
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
class WebFluxJacksonConfig {
|
class RestJacksonConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun pcpTimeModuleV3(): JacksonModule = PcpTimeModuleV3()
|
fun pcpTimeModuleV3(): JacksonModule = PcpTimeModuleV3()
|
||||||
+4
-10
@@ -6,8 +6,6 @@ import org.springframework.web.bind.annotation.PathVariable
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Schedulers
|
|
||||||
import space.nstart.pcp_tgu_service.dto.PlanResponse
|
import space.nstart.pcp_tgu_service.dto.PlanResponse
|
||||||
import space.nstart.pcp_tgu_service.service.PlanQueryService
|
import space.nstart.pcp_tgu_service.service.PlanQueryService
|
||||||
|
|
||||||
@@ -19,18 +17,14 @@ class PlanController(
|
|||||||
|
|
||||||
/** Возвращает планы по всем КА (persisted ∪ проекция будущего) с окном истории. */
|
/** Возвращает планы по всем КА (persisted ∪ проекция будущего) с окном истории. */
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getAllPlans(@RequestParam(defaultValue = "7") historyDays: Long): Mono<List<PlanResponse>> =
|
fun getAllPlans(@RequestParam(defaultValue = "7") historyDays: Long): List<PlanResponse> =
|
||||||
Mono.fromCallable {
|
planQueryService.getAllPlans(historyDays)
|
||||||
planQueryService.getAllPlans(historyDays)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Возвращает цепочку планов для одного КА (persisted ∪ проекция будущего). */
|
/** Возвращает цепочку планов для одного КА (persisted ∪ проекция будущего). */
|
||||||
@GetMapping("/{spacecraftId}")
|
@GetMapping("/{spacecraftId}")
|
||||||
fun getPlansBySpacecraftId(
|
fun getPlansBySpacecraftId(
|
||||||
@PathVariable spacecraftId: String,
|
@PathVariable spacecraftId: String,
|
||||||
@RequestParam(defaultValue = "7") historyDays: Long
|
@RequestParam(defaultValue = "7") historyDays: Long
|
||||||
): Mono<List<PlanResponse>> =
|
): List<PlanResponse> =
|
||||||
Mono.fromCallable {
|
planQueryService.getPlansBySpacecraftId(spacecraftId, historyDays)
|
||||||
planQueryService.getPlansBySpacecraftId(spacecraftId, historyDays)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -10,8 +10,6 @@ import org.springframework.web.bind.annotation.GetMapping
|
|||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Schedulers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* REST-контроллер для просмотра списка космических аппаратов.
|
* REST-контроллер для просмотра списка космических аппаратов.
|
||||||
@@ -30,10 +28,8 @@ class PlatformController(
|
|||||||
@Operation(summary = "Получить список космических аппаратов")
|
@Operation(summary = "Получить список космических аппаратов")
|
||||||
fun getPlatforms(
|
fun getPlatforms(
|
||||||
@RequestParam(required = false) status: String?
|
@RequestParam(required = false) status: String?
|
||||||
): Mono<List<PlatformResponse>> =
|
): List<PlatformResponse> =
|
||||||
Mono.fromCallable {
|
platformService.loadPlatforms(status).map { platform -> platform.toResponse() }
|
||||||
platformService.loadPlatforms(status).map { platform -> platform.toResponse() }
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
private fun PlatformCatalogItem.toResponse(): PlatformResponse =
|
private fun PlatformCatalogItem.toResponse(): PlatformResponse =
|
||||||
PlatformResponse(
|
PlatformResponse(
|
||||||
|
|||||||
+25
-40
@@ -9,8 +9,6 @@ import org.springframework.web.bind.annotation.RequestBody
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Schedulers
|
|
||||||
import space.nstart.pcp_tgu_service.domain.PlanDecision
|
import space.nstart.pcp_tgu_service.domain.PlanDecision
|
||||||
import space.nstart.pcp_tgu_service.dto.PlanDecisionMessage
|
import space.nstart.pcp_tgu_service.dto.PlanDecisionMessage
|
||||||
import space.nstart.pcp_tgu_service.dto.PlanResponse
|
import space.nstart.pcp_tgu_service.dto.PlanResponse
|
||||||
@@ -49,31 +47,25 @@ class TestTguController(
|
|||||||
fun refreshVisibility(
|
fun refreshVisibility(
|
||||||
@PathVariable spacecraftId: String,
|
@PathVariable spacecraftId: String,
|
||||||
@RequestParam(required = false) noradId: Long?
|
@RequestParam(required = false) noradId: Long?
|
||||||
): Mono<Void> =
|
) {
|
||||||
Mono.fromRunnable<Void> {
|
visibilityRefreshJobService.enqueue(spacecraftId, noradId, "TEST_CONTROLLER")
|
||||||
visibilityRefreshJobService.enqueue(spacecraftId, noradId, "TEST_CONTROLLER")
|
}
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Возвращает вычисленную on-demand проекцию будущей цепочки КА (диагностика). */
|
/** Возвращает вычисленную on-demand проекцию будущей цепочки КА (диагностика). */
|
||||||
@GetMapping("/spacecraft/{spacecraftId}/projection")
|
@GetMapping("/spacecraft/{spacecraftId}/projection")
|
||||||
fun getProjection(@PathVariable spacecraftId: String): Mono<List<PlanResponse>> =
|
fun getProjection(@PathVariable spacecraftId: String): List<PlanResponse> =
|
||||||
Mono.fromCallable {
|
planProjectionService.project(spacecraftId).map(PlanResponse::projected)
|
||||||
planProjectionService.project(spacecraftId).map(PlanResponse::projected)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Принудительно выдаёт конкретный persisted plan через новую attempt. */
|
/** Принудительно выдаёт конкретный persisted plan через новую attempt. */
|
||||||
@PostMapping("/plans/{planId}/issue")
|
@PostMapping("/plans/{planId}/issue")
|
||||||
fun issuePlan(@PathVariable planId: UUID): Mono<ProcessStartResponse> =
|
fun issuePlan(@PathVariable planId: UUID): ProcessStartResponse =
|
||||||
Mono.fromCallable {
|
issueDuePlanWorker.issuePlanNow(planId)
|
||||||
issueDuePlanWorker.issuePlanNow(planId)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Инжектит decision event через тот же use case, что Kafka consumer. */
|
/** Инжектит decision event через тот же use case, что Kafka consumer. */
|
||||||
@PostMapping("/plan-decisions")
|
@PostMapping("/plan-decisions")
|
||||||
fun handleDecision(@RequestBody message: PlanDecisionMessage): Mono<Void> =
|
fun handleDecision(@RequestBody message: PlanDecisionMessage) {
|
||||||
Mono.fromRunnable<Void> {
|
handlePlanDecisionUseCase.handle(message)
|
||||||
handlePlanDecisionUseCase.handle(message)
|
}
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Генерирует ACCEPTED/REJECTED decision event для active attempt указанного persisted plan. */
|
/** Генерирует ACCEPTED/REJECTED decision event для active attempt указанного persisted plan. */
|
||||||
@PostMapping("/plans/{planId}/decision")
|
@PostMapping("/plans/{planId}/decision")
|
||||||
@@ -81,23 +73,20 @@ class TestTguController(
|
|||||||
@PathVariable planId: UUID,
|
@PathVariable planId: UUID,
|
||||||
@RequestParam decision: PlanDecision,
|
@RequestParam decision: PlanDecision,
|
||||||
@RequestParam(required = false) reason: String?
|
@RequestParam(required = false) reason: String?
|
||||||
): Mono<PlanDecisionMessage> =
|
): PlanDecisionMessage =
|
||||||
Mono.fromCallable {
|
testPlanDecisionService.generateDecision(planId, decision, reason)
|
||||||
testPlanDecisionService.generateDecision(planId, decision, reason)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Загружает cache аппаратов из файла с root-array payload НСИ вместо запроса в НСИ. */
|
/** Загружает cache аппаратов из файла с root-array payload НСИ вместо запроса в НСИ. */
|
||||||
@PostMapping("/platforms/load-from-file")
|
@PostMapping("/platforms/load-from-file")
|
||||||
fun loadPlatformsFromFile(@RequestParam("path") filePath: String): Mono<TestPlatformsLoadResponse> =
|
fun loadPlatformsFromFile(@RequestParam("path") filePath: String): TestPlatformsLoadResponse {
|
||||||
Mono.fromCallable {
|
val path = Path.of(filePath).toAbsolutePath().normalize()
|
||||||
val path = Path.of(filePath).toAbsolutePath().normalize()
|
val platforms = platformService.loadPlatformsFromNsiPayload(Files.readString(path))
|
||||||
val platforms = platformService.loadPlatformsFromNsiPayload(Files.readString(path))
|
return TestPlatformsLoadResponse(
|
||||||
TestPlatformsLoadResponse(
|
filePath = path.toString(),
|
||||||
filePath = path.toString(),
|
loadedCount = platforms.size,
|
||||||
loadedCount = platforms.size,
|
allowedCount = platformService.loadAllowedPlatforms().size
|
||||||
allowedCount = platformService.loadAllowedPlatforms().size
|
)
|
||||||
)
|
}
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Загружает raw payload окон видимости из баллистики для диагностики. */
|
/** Загружает raw payload окон видимости из баллистики для диагностики. */
|
||||||
@GetMapping("/external-points/{noradId}")
|
@GetMapping("/external-points/{noradId}")
|
||||||
@@ -109,17 +98,13 @@ class TestTguController(
|
|||||||
@RequestParam("time_stop")
|
@RequestParam("time_stop")
|
||||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
|
||||||
timeStop: LocalDateTime
|
timeStop: LocalDateTime
|
||||||
): Mono<String> =
|
): String =
|
||||||
Mono.fromCallable {
|
externalPointsClient.fetchRawVisibilityPayload(noradId, timeStart, timeStop)
|
||||||
externalPointsClient.fetchRawVisibilityPayload(noradId, timeStart, timeStop)
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
|
|
||||||
/** Возвращает persisted plan для проверки результата ручных операций. */
|
/** Возвращает persisted plan для проверки результата ручных операций. */
|
||||||
@GetMapping("/plans/{planId}")
|
@GetMapping("/plans/{planId}")
|
||||||
fun getPlan(@PathVariable planId: UUID): Mono<PlanResponse> =
|
fun getPlan(@PathVariable planId: UUID): PlanResponse =
|
||||||
Mono.fromCallable {
|
PlanResponse.from(planQueryService.getByPlanId(planId))
|
||||||
PlanResponse.from(planQueryService.getByPlanId(planId))
|
|
||||||
}.subscribeOn(Schedulers.boundedElastic())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TestPlatformsLoadResponse(
|
data class TestPlatformsLoadResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user