feat: build vehicle data platform and production pipeline

This commit is contained in:
lingniu
2026-07-14 12:35:33 +08:00
parent b452be3b94
commit bb59303a4b
270 changed files with 88016 additions and 1975 deletions

View File

@@ -0,0 +1,40 @@
-- Forward-only V2 access threshold schema. Safe to run repeatedly before API switch.
CREATE TABLE IF NOT EXISTS vehicle_access_threshold_config (
id TINYINT NOT NULL PRIMARY KEY,
version INT NOT NULL,
default_threshold_sec INT NOT NULL,
delay_threshold_sec INT NOT NULL,
long_offline_sec INT NOT NULL,
protocol_overrides_json LONGTEXT NOT NULL,
updated_by VARCHAR(128) NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS vehicle_access_threshold_audit (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
version INT NOT NULL,
actor VARCHAR(128) NOT NULL,
summary VARCHAR(255) NOT NULL,
config_json LONGTEXT NOT NULL,
changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_access_threshold_audit_version (version),
KEY idx_access_threshold_audit_changed (changed_at)
);
INSERT IGNORE INTO vehicle_access_threshold_config (
id,
version,
default_threshold_sec,
delay_threshold_sec,
long_offline_sec,
protocol_overrides_json,
updated_by
) VALUES (
1,
1,
300,
30,
1800,
'[{"protocol":"GB32960","thresholdSec":300},{"protocol":"JT808","thresholdSec":300},{"protocol":"YUTONG_MQTT","thresholdSec":600}]',
'migration'
);

View File

@@ -0,0 +1,116 @@
-- Durable alert center. Apply before switching API/evaluator binaries.
CREATE TABLE IF NOT EXISTS vehicle_alert_rule (
id VARCHAR(64) NOT NULL PRIMARY KEY,
name VARCHAR(80) NOT NULL,
description VARCHAR(500) NOT NULL DEFAULT '',
severity ENUM('critical','major','minor') NOT NULL,
value_type ENUM('numeric','boolean') NOT NULL,
metric VARCHAR(64) NOT NULL,
operator VARCHAR(8) NOT NULL,
threshold_value DOUBLE NOT NULL DEFAULT 0,
boolean_threshold TINYINT NULL,
duration_sec INT NOT NULL DEFAULT 0,
recovery_operator VARCHAR(8) NOT NULL DEFAULT '',
recovery_threshold DOUBLE NOT NULL DEFAULT 0,
repeat_interval_sec INT NOT NULL DEFAULT 0,
scope_protocols_json TEXT NOT NULL,
scope_vins_json LONGTEXT NOT NULL,
notification_channels_json TEXT NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
version INT NOT NULL DEFAULT 1,
created_by VARCHAR(128) NOT NULL,
updated_by VARCHAR(128) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_alert_rule_enabled (enabled, severity),
KEY idx_alert_rule_metric (metric)
);
CREATE TABLE IF NOT EXISTS vehicle_alert_rule_audit (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
rule_id VARCHAR(64) NOT NULL,
rule_version INT NOT NULL,
actor VARCHAR(128) NOT NULL,
action VARCHAR(32) NOT NULL,
snapshot_json LONGTEXT NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
KEY idx_alert_rule_audit_rule (rule_id, rule_version),
KEY idx_alert_rule_audit_created (created_at)
);
CREATE TABLE IF NOT EXISTS vehicle_alert_candidate (
rule_id VARCHAR(64) NOT NULL,
vin VARCHAR(64) NOT NULL,
protocol VARCHAR(32) NOT NULL,
first_matched_at DATETIME(3) NOT NULL,
last_matched_at DATETIME(3) NOT NULL,
latest_value DOUBLE NOT NULL,
source_event_id VARCHAR(128) NOT NULL DEFAULT '',
PRIMARY KEY (rule_id, vin, protocol),
KEY idx_alert_candidate_duration (first_matched_at, last_matched_at)
);
CREATE TABLE IF NOT EXISTS vehicle_alert_event (
id VARCHAR(64) NOT NULL PRIMARY KEY,
fingerprint VARCHAR(255) NOT NULL,
rule_id VARCHAR(64) NOT NULL,
rule_name VARCHAR(80) NOT NULL,
rule_version INT NOT NULL,
severity ENUM('critical','major','minor') NOT NULL,
status ENUM('unprocessed','processing','recovered','closed','ignored') NOT NULL,
vin VARCHAR(64) NOT NULL,
plate VARCHAR(64) NOT NULL DEFAULT '',
protocol VARCHAR(32) NOT NULL DEFAULT '',
metric VARCHAR(64) NOT NULL,
operator VARCHAR(8) NOT NULL,
trigger_value DOUBLE NOT NULL,
threshold_value DOUBLE NOT NULL,
unit VARCHAR(24) NOT NULL DEFAULT '',
duration_sec INT NOT NULL DEFAULT 0,
location_text VARCHAR(255) NOT NULL DEFAULT '',
longitude DOUBLE NULL,
latitude DOUBLE NULL,
source_event_id VARCHAR(128) NOT NULL DEFAULT '',
event_at DATETIME(3) NULL,
received_at DATETIME(3) NULL,
triggered_at DATETIME(3) NOT NULL,
recovered_at DATETIME(3) NULL,
handler VARCHAR(128) NOT NULL DEFAULT '',
version INT NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_alert_event_list (triggered_at, id),
KEY idx_alert_event_status (status, severity, triggered_at),
KEY idx_alert_event_vehicle (vin, triggered_at),
KEY idx_alert_event_rule (rule_id, triggered_at),
KEY idx_alert_event_fingerprint (fingerprint, status)
);
CREATE TABLE IF NOT EXISTS vehicle_alert_event_action (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
event_id VARCHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL,
from_status VARCHAR(32) NOT NULL DEFAULT '',
to_status VARCHAR(32) NOT NULL,
actor VARCHAR(128) NOT NULL,
note VARCHAR(200) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
KEY idx_alert_action_event (event_id, created_at),
KEY idx_alert_action_actor (actor, created_at)
);
CREATE TABLE IF NOT EXISTS vehicle_alert_notification (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
event_id VARCHAR(64) NOT NULL,
title VARCHAR(120) NOT NULL,
content VARCHAR(500) NOT NULL,
severity ENUM('critical','major','minor') NOT NULL,
channel ENUM('in_app','sms','email','wecom') NOT NULL DEFAULT 'in_app',
delivery_status ENUM('created','reserved','sent','failed') NOT NULL DEFAULT 'created',
is_read TINYINT(1) NOT NULL DEFAULT 0,
read_by VARCHAR(128) NOT NULL DEFAULT '',
read_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
KEY idx_alert_notification_unread (channel, is_read, created_at),
KEY idx_alert_notification_event (event_id)
);

View File

@@ -0,0 +1,16 @@
ALTER TABLE vehicle_alert_rule
ADD COLUMN threshold_high DOUBLE NOT NULL DEFAULT 0 AFTER threshold_value,
ADD COLUMN scope_oems_json TEXT NULL AFTER scope_vins_json;
ALTER TABLE vehicle_alert_event
ADD COLUMN threshold_high DOUBLE NOT NULL DEFAULT 0 AFTER threshold_value;
CREATE TABLE IF NOT EXISTS vehicle_alert_rule_state (
rule_id VARCHAR(64) NOT NULL,
vin VARCHAR(64) NOT NULL,
protocol VARCHAR(32) NOT NULL,
observed_value DOUBLE NOT NULL,
last_observed_at DATETIME(3) NOT NULL,
PRIMARY KEY (rule_id, vin, protocol),
KEY idx_alert_rule_state_observed (last_observed_at)
);

View File

@@ -0,0 +1,2 @@
ALTER TABLE vehicle_alert_event
ADD KEY idx_alert_event_repeat (fingerprint, triggered_at);

View File

@@ -0,0 +1,53 @@
CREATE TABLE IF NOT EXISTS vehicle_metric_definition (
metric_key VARCHAR(64) NOT NULL PRIMARY KEY,
label VARCHAR(80) NOT NULL,
description VARCHAR(255) NOT NULL DEFAULT '',
unit VARCHAR(24) NOT NULL DEFAULT '',
category VARCHAR(40) NOT NULL,
value_type VARCHAR(16) NOT NULL,
protocols_json VARCHAR(512) NOT NULL,
searchable TINYINT(1) NOT NULL DEFAULT 0,
chartable TINYINT(1) NOT NULL DEFAULT 0,
alertable TINYINT(1) NOT NULL DEFAULT 0,
enabled TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 100,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_metric_definition_capability (enabled, alertable, category, sort_order)
);
CREATE TABLE IF NOT EXISTS vehicle_metric_protocol_mapping (
metric_key VARCHAR(64) NOT NULL,
protocol VARCHAR(32) NOT NULL,
source_field VARCHAR(160) NOT NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (metric_key, protocol),
CONSTRAINT fk_metric_mapping_definition FOREIGN KEY (metric_key) REFERENCES vehicle_metric_definition(metric_key)
);
INSERT IGNORE INTO vehicle_metric_definition
(metric_key,label,description,unit,category,value_type,protocols_json,searchable,chartable,alertable,enabled,sort_order) VALUES
('speed_kmh','速度','车辆最新行驶速度','km/h','driving','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,1,1,1,10),
('soc_percent','SOC','动力电池剩余电量百分比','%','energy','numeric','["GB32960","YUTONG_MQTT"]',1,1,1,1,20),
('alarm_active','协议告警位','协议报文是否携带活动告警','','safety','boolean','["GB32960","JT808","YUTONG_MQTT"]',1,0,1,1,30),
('freshness_sec','离线时长','最新数据距当前时间的秒数','s','quality','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,1,1,1,40),
('data_delay_sec','数据延迟','服务接收时间与设备事件时间的差值','s','quality','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,1,1,1,50),
('total_mileage_km','总里程','车辆累计行驶里程','km','driving','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,1,0,1,60),
('longitude','经度','WGS84/协议归一化经度','°','location','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,0,0,1,70),
('latitude','纬度','WGS84/协议归一化纬度','°','location','numeric','["GB32960","JT808","YUTONG_MQTT"]',1,0,0,1,80);
INSERT IGNORE INTO vehicle_metric_protocol_mapping(metric_key,protocol,source_field) VALUES
('speed_kmh','GB32960','gb32960.vehicle.speed_kmh'),
('speed_kmh','JT808','jt808.location.speed_kmh'),
('speed_kmh','YUTONG_MQTT','yutong.vehicle.speed_kmh'),
('soc_percent','GB32960','gb32960.vehicle.soc_percent'),
('soc_percent','YUTONG_MQTT','yutong.vehicle.soc_percent'),
('alarm_active','GB32960','gb32960.vehicle.alarm_flag'),
('alarm_active','JT808','jt808.location.alarm_flag'),
('alarm_active','YUTONG_MQTT','yutong.vehicle.alarm_flag'),
('freshness_sec','PLATFORM','vehicle_realtime_location.updated_at'),
('data_delay_sec','PLATFORM','received_at-event_time'),
('total_mileage_km','GB32960','gb32960.vehicle.total_mileage_km'),
('total_mileage_km','JT808','jt808.location.total_mileage_km'),
('total_mileage_km','YUTONG_MQTT','yutong.vehicle.total_mileage_km'),
('longitude','PLATFORM','vehicle_realtime_location.longitude'),
('latitude','PLATFORM','vehicle_realtime_location.latitude');

View File

@@ -0,0 +1,34 @@
-- Supplemental vehicle master data. Gateway-owned VIN/plate/OEM identity remains authoritative.
CREATE TABLE IF NOT EXISTS vehicle_profile (
vin VARCHAR(32) NOT NULL PRIMARY KEY,
model_name VARCHAR(128) NOT NULL DEFAULT '',
vehicle_type VARCHAR(64) NOT NULL DEFAULT '',
company_name VARCHAR(128) NOT NULL DEFAULT '',
operation_status VARCHAR(32) NOT NULL DEFAULT 'unknown',
access_provider VARCHAR(128) NOT NULL DEFAULT '',
first_access_at DATETIME(3) NULL,
runtime_seconds BIGINT UNSIGNED NULL,
source_system VARCHAR(64) NOT NULL DEFAULT 'manual',
source_version VARCHAR(128) NOT NULL DEFAULT '',
synced_at DATETIME(3) NULL,
version INT NOT NULL DEFAULT 1,
updated_by VARCHAR(128) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
KEY idx_vehicle_profile_company (company_name),
KEY idx_vehicle_profile_model (model_name),
KEY idx_vehicle_profile_status (operation_status),
KEY idx_vehicle_profile_provider (access_provider)
);
CREATE TABLE IF NOT EXISTS vehicle_profile_audit (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
vin VARCHAR(32) NOT NULL,
profile_version INT NOT NULL,
actor VARCHAR(128) NOT NULL,
action VARCHAR(32) NOT NULL,
snapshot_json LONGTEXT NOT NULL,
changed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_vehicle_profile_audit_version (vin, profile_version),
KEY idx_vehicle_profile_audit_changed (changed_at)
);

View File

@@ -0,0 +1,6 @@
-- Add authoritative vehicle master-data dimensions to alert rule scope.
ALTER TABLE vehicle_alert_rule
ADD COLUMN scope_models_json TEXT NULL AFTER scope_oems_json;
ALTER TABLE vehicle_alert_rule
ADD COLUMN scope_companies_json TEXT NULL AFTER scope_models_json;

View File

@@ -0,0 +1,22 @@
-- Durable access evidence written atomically by the gateway realtime snapshot writer.
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_first_seen_at DATETIME(3) NULL AFTER event_id;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_previous_received_at DATETIME(3) NULL AFTER access_first_seen_at;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_latest_received_at DATETIME(3) NULL AFTER access_previous_received_at;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_report_interval_ms BIGINT UNSIGNED NULL AFTER access_latest_received_at;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_sample_count BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER access_report_interval_ms;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_latest_event_id VARCHAR(64) NOT NULL DEFAULT '' AFTER access_sample_count;
ALTER TABLE vehicle_realtime_snapshot ADD COLUMN access_first_seen_source VARCHAR(32) NOT NULL DEFAULT '' AFTER access_latest_event_id;
UPDATE vehicle_realtime_snapshot
SET access_first_seen_at = COALESCE(access_first_seen_at, received_at, updated_at),
access_latest_received_at = COALESCE(access_latest_received_at, received_at, updated_at),
access_sample_count = IF(access_sample_count = 0, 1, access_sample_count),
access_latest_event_id = IF(access_latest_event_id = '', event_id, access_latest_event_id),
access_first_seen_source = IF(access_first_seen_source = '', 'snapshot_backfill', access_first_seen_source)
WHERE access_first_seen_at IS NULL
OR access_latest_received_at IS NULL
OR access_sample_count = 0
OR access_latest_event_id = ''
OR access_first_seen_source = '';
CREATE INDEX idx_access_latest ON vehicle_realtime_snapshot(access_latest_received_at, vin);

View File

@@ -0,0 +1,22 @@
-- Durable Kafka checkpoint for the event-time alert stream shadow/active worker.
-- Business alert effects and next_offset will share one MySQL transaction when
-- active evaluation is enabled. Kafka group offsets are committed afterwards.
CREATE TABLE IF NOT EXISTS vehicle_alert_stream_checkpoint (
consumer_group VARCHAR(128) NOT NULL,
topic VARCHAR(255) NOT NULL,
partition_id INT NOT NULL,
next_offset BIGINT NOT NULL,
high_watermark BIGINT NOT NULL DEFAULT 0,
processed_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
valid_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
invalid_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
late_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
replay_skipped_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
last_event_at DATETIME(3) NULL,
last_received_at DATETIME(3) NULL,
last_source_event_id VARCHAR(128) NOT NULL DEFAULT '',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (consumer_group, topic, partition_id),
KEY idx_alert_stream_updated (updated_at),
KEY idx_alert_stream_lag (consumer_group, high_watermark, next_offset)
);

View File

@@ -0,0 +1,30 @@
-- Align alertable metric mappings with the canonical fields emitted by each
-- production protocol parser. This is forward-only because migration 005 may
-- already be journaled on an ECS.
UPDATE vehicle_metric_protocol_mapping
SET source_field='yutong_mqtt.data.meter_speed'
WHERE metric_key='speed_kmh' AND protocol='YUTONG_MQTT';
UPDATE vehicle_metric_protocol_mapping
SET source_field='yutong_mqtt.data.battery_capacity_soc'
WHERE metric_key='soc_percent' AND protocol='YUTONG_MQTT';
UPDATE vehicle_metric_protocol_mapping
SET source_field='gb32960.alarm.general_alarm_flag'
WHERE metric_key='alarm_active' AND protocol='GB32960';
DELETE FROM vehicle_metric_protocol_mapping
WHERE metric_key='alarm_active' AND protocol='YUTONG_MQTT';
UPDATE vehicle_metric_definition
SET protocols_json='["GB32960","JT808"]'
WHERE metric_key='alarm_active';
INSERT INTO vehicle_metric_protocol_mapping(metric_key,protocol,source_field) VALUES
('longitude','GB32960','gb32960.position.longitude'),
('longitude','JT808','jt808.location.longitude'),
('longitude','YUTONG_MQTT','yutong_mqtt.data.longitude'),
('latitude','GB32960','gb32960.position.latitude'),
('latitude','JT808','jt808.location.latitude'),
('latitude','YUTONG_MQTT','yutong_mqtt.data.latitude')
ON DUPLICATE KEY UPDATE source_field=VALUES(source_field);

View File

@@ -0,0 +1,6 @@
ALTER TABLE vehicle_alert_stream_checkpoint
ADD COLUMN last_invalid_code VARCHAR(64) NOT NULL DEFAULT '' AFTER last_source_event_id,
ADD COLUMN last_invalid_at DATETIME(3) NULL AFTER last_invalid_code;
CREATE INDEX idx_alert_stream_invalid
ON vehicle_alert_stream_checkpoint(consumer_group,last_invalid_at);

View File

@@ -0,0 +1,20 @@
[Unit]
Description=Lingniu Vehicle Alert Evaluator
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/lingniu-vehicle-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
ExecStart=/opt/lingniu-vehicle-platform/current/alert-evaluator
Restart=always
RestartSec=3
TimeoutStopSec=20
NoNewPrivileges=true
PrivateTmp=true
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,20 @@
[Unit]
Description=Lingniu Vehicle Alert Kafka Stream Evaluator
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/lingniu-vehicle-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
ExecStart=/opt/lingniu-vehicle-platform/current/alert-stream-evaluator
Restart=always
RestartSec=3
TimeoutStopSec=30
NoNewPrivileges=true
PrivateTmp=true
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -6,6 +6,7 @@ Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/lingniu-vehicle-platform/current
EnvironmentFile=/opt/lingniu-go-native/env/base.env
EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
ExecStart=/opt/lingniu-vehicle-platform/current/platform-api
Restart=always