Основные изменения:

состояние окон видимости, расписания планов, попыток выдачи и job’ов перенесено в БД;
план и попытка выдачи разделены на разные сущности;
Kafka-события теперь двигают persisted state/jobs, а не локальную память pod’а;
BPMN createSatelliteMission теперь отвечает только за одну попытку выдачи плана;
обновление слотов после принятия плана вынесено в отдельный BPMN-процесс;
workers используют DB claim/lock model и безопасны для нескольких pod’ов;
тестовый ручной API вынесен в отдельный disabled-by-default controller
This commit is contained in:
Дмитрий Соловьев
2026-05-27 10:29:41 +03:00
parent cf3bb63bc1
commit 3dce0aec0d
89 changed files with 3922 additions and 1953 deletions
@@ -1,124 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_05089qd" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.41.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.8.0">
<bpmn:process id="createSatelliteMission" isExecutable="true">
<bpmn:endEvent id="endSatelliteMission">
<bpmn:incoming>Flow_15fg4ts</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0i87yhe" sourceRef="calculateSatelliteSurveyMissions" targetRef="calculateSatelliteDropMissions" />
<bpmn:sequenceFlow id="Flow_0m0ymgb" sourceRef="sendSatellitePlan" targetRef="satellitePlanAccepted" />
<bpmn:sequenceFlow id="Flow_0r63sxk" sourceRef="satellitePlanAccepted" targetRef="updatePlanStatus" />
<bpmn:sequenceFlow id="Flow_105c46r" sourceRef="startSatelliteMission" targetRef="calculateSatelliteSurveyMissions" />
<bpmn:sequenceFlow id="Flow_141jv88" sourceRef="calculateSatelliteDropMissions" targetRef="sendSatellitePlan" />
<bpmn:sequenceFlow id="Flow_15fg4ts" sourceRef="updateSlotsStatus" targetRef="endSatelliteMission" />
<bpmn:sequenceFlow id="Flow_15tmgj4" sourceRef="updatePlanStatus" targetRef="updateSlotsStatus" />
<bpmn:intermediateCatchEvent id="satellitePlanAccepted">
<bpmn:incoming>Flow_0m0ymgb</bpmn:incoming>
<bpmn:outgoing>Flow_0r63sxk</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_0wt6aih" messageRef="Message_1vkej6h" />
</bpmn:intermediateCatchEvent>
<bpmn:startEvent id="startSatelliteMission">
<bpmn:outgoing>Flow_105c46r</bpmn:outgoing>
<bpmn:outgoing>Flow_start_survey</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="updatePlanStatus" name="Обновление статуса плана">
<bpmn:serviceTask id="calculateSatelliteSurveyMissions" name="Сформировать план съемки">
<bpmn:extensionElements>
<zeebe:taskDefinition type="updatePlanStatus" />
<zeebe:taskDefinition type="calculateSatelliteSurveyMissions" retries="3" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_0r63sxk</bpmn:incoming>
<bpmn:outgoing>Flow_15tmgj4</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="updateSlotsStatus" name="Обновление статуса слотов">
<bpmn:extensionElements>
<zeebe:taskDefinition type="updateSlotsStatus" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_15tmgj4</bpmn:incoming>
<bpmn:outgoing>Flow_15fg4ts</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="sendSatellitePlan" name="Отправка плана в СПО УОГ">
<bpmn:extensionElements>
<zeebe:taskDefinition type="sendSatellitePlan" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_141jv88</bpmn:incoming>
<bpmn:outgoing>Flow_0m0ymgb</bpmn:outgoing>
<bpmn:incoming>Flow_start_survey</bpmn:incoming>
<bpmn:outgoing>Flow_survey_drop</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="calculateSatelliteDropMissions" name="Сформировать план сбросов">
<bpmn:extensionElements>
<zeebe:taskDefinition type="calculateSatelliteDropMissions" retries="3" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_0i87yhe</bpmn:incoming>
<bpmn:outgoing>Flow_141jv88</bpmn:outgoing>
<bpmn:incoming>Flow_survey_drop</bpmn:incoming>
<bpmn:outgoing>Flow_drop_send</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="calculateSatelliteSurveyMissions" name="Сформировать план съемки">
<bpmn:serviceTask id="sendSatellitePlan" name="Отправка плана в СПО УОГ">
<bpmn:extensionElements>
<zeebe:taskDefinition type="calculateSatelliteSurveyMissions" retries="3" />
<zeebe:taskDefinition type="sendSatellitePlan" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_105c46r</bpmn:incoming>
<bpmn:outgoing>Flow_0i87yhe</bpmn:outgoing>
<bpmn:incoming>Flow_drop_send</bpmn:incoming>
<bpmn:outgoing>Flow_send_end</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="endSatelliteMission">
<bpmn:incoming>Flow_send_end</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_start_survey" sourceRef="startSatelliteMission" targetRef="calculateSatelliteSurveyMissions" />
<bpmn:sequenceFlow id="Flow_survey_drop" sourceRef="calculateSatelliteSurveyMissions" targetRef="calculateSatelliteDropMissions" />
<bpmn:sequenceFlow id="Flow_drop_send" sourceRef="calculateSatelliteDropMissions" targetRef="sendSatellitePlan" />
<bpmn:sequenceFlow id="Flow_send_end" sourceRef="sendSatellitePlan" targetRef="endSatelliteMission" />
</bpmn:process>
<bpmn:message id="Message_0caslea" />
<bpmn:message id="Message_39ltke4" />
<bpmn:message id="Message_0nothk0" />
<bpmn:message id="Message_1vkej6h" name="Message_SatellitePlanAccepted">
<bpmn:extensionElements>
<zeebe:subscription correlationKey="=satellitePlanId" />
</bpmn:extensionElements>
</bpmn:message>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="createSatelliteMission">
<bpmndi:BPMNShape id="Event_08dmvzc_di" bpmnElement="satellitePlanAccepted">
<dc:Bounds x="752" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="startSatelliteMission">
<dc:Bounds x="182" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1o0a7ou_di" bpmnElement="calculateSatelliteSurveyMissions">
<dc:Bounds x="270" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xhz2da_di" bpmnElement="calculateSatelliteDropMissions">
<dc:Bounds x="430" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0rgmxq5_di" bpmnElement="sendSatellitePlan">
<dc:Bounds x="590" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0xhz2da_di" bpmnElement="calculateSatelliteDropMissions">
<dc:Bounds x="430" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1o0a7ou_di" bpmnElement="calculateSatelliteSurveyMissions">
<dc:Bounds x="270" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1yorqot_di" bpmnElement="updatePlanStatus">
<dc:Bounds x="840" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01df1b7_di" bpmnElement="updateSlotsStatus">
<dc:Bounds x="1000" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ua03fi_di" bpmnElement="endSatelliteMission">
<dc:Bounds x="1152" y="102" width="36" height="36" />
<dc:Bounds x="752" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0i87yhe_di" bpmnElement="Flow_0i87yhe">
<di:waypoint x="370" y="120" />
<di:waypoint x="430" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0m0ymgb_di" bpmnElement="Flow_0m0ymgb">
<di:waypoint x="690" y="120" />
<di:waypoint x="752" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_105c46r_di" bpmnElement="Flow_105c46r">
<bpmndi:BPMNEdge id="Flow_start_survey_di" bpmnElement="Flow_start_survey">
<di:waypoint x="218" y="120" />
<di:waypoint x="270" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_141jv88_di" bpmnElement="Flow_141jv88">
<bpmndi:BPMNEdge id="Flow_survey_drop_di" bpmnElement="Flow_survey_drop">
<di:waypoint x="370" y="120" />
<di:waypoint x="430" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_drop_send_di" bpmnElement="Flow_drop_send">
<di:waypoint x="530" y="120" />
<di:waypoint x="590" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0r63sxk_di" bpmnElement="Flow_0r63sxk">
<di:waypoint x="788" y="120" />
<di:waypoint x="840" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_15fg4ts_di" bpmnElement="Flow_15fg4ts">
<di:waypoint x="1100" y="120" />
<di:waypoint x="1152" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_15tmgj4_di" bpmnElement="Flow_15tmgj4">
<di:waypoint x="940" y="120" />
<di:waypoint x="1000" y="120" />
<bpmndi:BPMNEdge id="Flow_send_end_di" bpmnElement="Flow_send_end">
<di:waypoint x="690" y="120" />
<di:waypoint x="752" y="120" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_updateAcceptedPlanSlots" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.41.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.8.0">
<bpmn:process id="updateAcceptedPlanSlots" isExecutable="true">
<bpmn:startEvent id="startUpdateAcceptedPlanSlots">
<bpmn:outgoing>Flow_start_update_slots</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="updateSlotsStatus" name="Обновление статуса слотов">
<bpmn:extensionElements>
<zeebe:taskDefinition type="updateSlotsStatus" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_start_update_slots</bpmn:incoming>
<bpmn:outgoing>Flow_update_slots_end</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="endUpdateAcceptedPlanSlots">
<bpmn:incoming>Flow_update_slots_end</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_start_update_slots" sourceRef="startUpdateAcceptedPlanSlots" targetRef="updateSlotsStatus" />
<bpmn:sequenceFlow id="Flow_update_slots_end" sourceRef="updateSlotsStatus" targetRef="endUpdateAcceptedPlanSlots" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_updateAcceptedPlanSlots">
<bpmndi:BPMNPlane id="BPMNPlane_updateAcceptedPlanSlots" bpmnElement="updateAcceptedPlanSlots">
<bpmndi:BPMNShape id="StartEvent_update_slots_di" bpmnElement="startUpdateAcceptedPlanSlots">
<dc:Bounds x="182" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_updateSlotsStatus_di" bpmnElement="updateSlotsStatus">
<dc:Bounds x="270" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_update_slots_di" bpmnElement="endUpdateAcceptedPlanSlots">
<dc:Bounds x="432" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_start_update_slots_di" bpmnElement="Flow_start_update_slots">
<di:waypoint x="218" y="120" />
<di:waypoint x="270" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_update_slots_end_di" bpmnElement="Flow_update_slots_end">
<di:waypoint x="370" y="120" />
<di:waypoint x="432" y="120" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
@@ -11,3 +11,23 @@ spring:
fail-fast: ${CONFIG_SERVER_FAIL_FAST:true}
profile: ${SPRING_CLOUD_CONFIG_PROFILE:${SPRING_PROFILES_ACTIVE:${spring.profiles.default}}}
label: ${SPRING_CLOUD_CONFIG_LABEL:master}
planning:
notification-before-start-minutes: 263
target-plan-duration-minutes: 180
target-plan-duration-delta-minutes: 60
external-update-enabled: true
external-points-lookahead-days: 3
in-memory-history-days: 14
external-update-fixed-delay-ms: 600000
notification-check-fixed-delay-ms: 30000
min-reissue-lead-minutes: 5
worker-lock-ttl-seconds: 300
topics:
visibility-windows-changed: pcp.tgu.visibility-windows-changed.v1
satellite-plan-decision: pcp.tgu.satellite-plan-decision.v1
tgu:
test-controller:
enabled: false
@@ -0,0 +1,105 @@
CREATE TABLE IF NOT EXISTS spacecraft_snapshot (
spacecraft_id VARCHAR(255) PRIMARY KEY,
norad_id BIGINT NOT NULL,
nsi_id VARCHAR(255),
business_key VARCHAR(255),
name VARCHAR(1024),
status VARCHAR(255),
mission VARCHAR(1024),
valid_from TIMESTAMP,
valid_to TIMESTAMP,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS spacecraft_visibility_snapshot (
spacecraft_id VARCHAR(255) PRIMARY KEY,
norad_id BIGINT NOT NULL,
payload_json TEXT NOT NULL,
payload_hash VARCHAR(128) NOT NULL,
fetched_at TIMESTAMP NOT NULL,
visibility_from TIMESTAMP,
visibility_to TIMESTAMP,
windows_count INTEGER,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS planned_plan (
plan_id UUID PRIMARY KEY,
spacecraft_id VARCHAR(255) NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
kpp_id VARCHAR(255) NOT NULL,
status VARCHAR(64) NOT NULL,
issue_not_before TIMESTAMP,
base_plan_id UUID,
chain_version BIGINT NOT NULL DEFAULT 0,
accepted_at TIMESTAMP,
superseded_by_plan_id UUID,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
CONSTRAINT uq_planned_plan_business_key UNIQUE (spacecraft_id, start_time, end_time, kpp_id)
);
CREATE INDEX IF NOT EXISTS idx_planned_plan_due
ON planned_plan (status, issue_not_before, start_time);
CREATE INDEX IF NOT EXISTS idx_planned_plan_spacecraft_start
ON planned_plan (spacecraft_id, start_time);
CREATE TABLE IF NOT EXISTS plan_issue_attempt (
attempt_id UUID PRIMARY KEY,
plan_id UUID NOT NULL REFERENCES planned_plan(plan_id),
attempt_no INTEGER NOT NULL,
status VARCHAR(64) NOT NULL,
process_instance_key BIGINT,
decision_event_id UUID,
decision_reason TEXT,
started_at TIMESTAMP,
finished_at TIMESTAMP,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
CONSTRAINT uq_plan_issue_attempt_no UNIQUE (plan_id, attempt_no)
);
CREATE INDEX IF NOT EXISTS idx_plan_issue_attempt_plan
ON plan_issue_attempt (plan_id, attempt_no);
CREATE TABLE IF NOT EXISTS visibility_refresh_job (
spacecraft_id VARCHAR(255) PRIMARY KEY,
norad_id BIGINT,
status VARCHAR(64) NOT NULL,
reason VARCHAR(255),
locked_by VARCHAR(255),
locked_until TIMESTAMP,
attempts INTEGER NOT NULL DEFAULT 0,
requested_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS plan_recalculation_job (
spacecraft_id VARCHAR(255) PRIMARY KEY,
status VARCHAR(64) NOT NULL,
reason VARCHAR(255),
locked_by VARCHAR(255),
locked_until TIMESTAMP,
attempts INTEGER NOT NULL DEFAULT 0,
requested_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS processed_plan_decision_event (
event_id UUID PRIMARY KEY,
received_at TIMESTAMP NOT NULL
);
ALTER TABLE spacecraft_planning_state
ADD COLUMN IF NOT EXISTS active_plan_id UUID;
ALTER TABLE spacecraft_planning_state
ADD COLUMN IF NOT EXISTS active_attempt_id UUID;
ALTER TABLE spacecraft_planning_state
ADD COLUMN IF NOT EXISTS chain_version BIGINT NOT NULL DEFAULT 0;
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS accepted_plan_slots_update_job (
plan_id UUID PRIMARY KEY,
status VARCHAR(32) NOT NULL,
process_instance_key BIGINT NULL,
locked_by VARCHAR(255) NULL,
locked_until TIMESTAMP NULL,
attempts INT NOT NULL DEFAULT 0,
last_error TEXT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
@@ -0,0 +1,5 @@
ALTER TABLE visibility_refresh_job
ADD COLUMN IF NOT EXISTS rerun_requested BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE plan_recalculation_job
ADD COLUMN IF NOT EXISTS rerun_requested BOOLEAN NOT NULL DEFAULT FALSE;