diff --git a/deploy/local/launchctl/README.md b/deploy/local/launchctl/README.md index 9a4cee7c..6b03d4bf 100644 --- a/deploy/local/launchctl/README.md +++ b/deploy/local/launchctl/README.md @@ -1,10 +1,12 @@ # macOS launchctl 本机部署模板 -本目录用于把本机 32960/JT808/history 三个服务的启动方式仓库化。模板面向当前生产验证链路: +本目录用于把本机生产验证服务的启动方式仓库化。模板面向当前生产验证链路: - `gb32960-ingest-app`: TCP `32960`,HTTP `20100` - `jt808-ingest-app`: TCP `808`,HTTP `20400` +- `yutong-mqtt-app`: HTTP `20500` - `vehicle-history-app`: HTTP `20200` +- `vehicle-analytics-app`: HTTP `20300` `vehicle-history-app` 的热历史查询只依赖 Kafka 与 TDengine:RAW 帧的 `parsedJson`、`metadataJson`、`rawUri` 已进入 TDengine `raw_frames`。GB32960/JT808 接入服务可以继续保留 `SINK_ARCHIVE_PATH` 作为原始 `.bin` 冷备,history 本机模板不再要求共享 archive root。 @@ -30,11 +32,19 @@ export VEHICLE_IDENTITY_MYSQL_JDBC_URL='jdbc:mysql://:3306/vehicle_i export VEHICLE_IDENTITY_MYSQL_USERNAME='' export VEHICLE_IDENTITY_MYSQL_PASSWORD='' export VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL='60s' +export MYSQL_JDBC_URL="$VEHICLE_IDENTITY_MYSQL_JDBC_URL" +export MYSQL_USERNAME="$VEHICLE_IDENTITY_MYSQL_USERNAME" +export MYSQL_PASSWORD="$VEHICLE_IDENTITY_MYSQL_PASSWORD" +export YUTONG_MQTT_ENABLED='false' +export YUTONG_MQTT_URI='' +export YUTONG_MQTT_TOPIC='#' +export YUTONG_MQTT_USERNAME='' +export YUTONG_MQTT_PASSWORD='' mkdir -p "$HOME/Library/LaunchAgents" "$PROJECT_ROOT/data" -mkdir -p /tmp/lingniu-gb32960-live /tmp/lingniu-jt808-live /tmp/lingniu-history-live +mkdir -p /tmp/lingniu-gb32960-live /tmp/lingniu-jt808-live /tmp/lingniu-yutong-mqtt-live /tmp/lingniu-vehicle-history-live /tmp/lingniu-vehicle-analytics-live -for service in gb32960 jt808 vehicle-history; do +for service in gb32960 jt808 yutong-mqtt vehicle-history vehicle-analytics; do sed \ -e "s#__PROJECT_ROOT__#$PROJECT_ROOT#g" \ -e "s#__JAVA_BIN__#$JAVA_BIN#g" \ @@ -46,6 +56,14 @@ for service in gb32960 jt808 vehicle-history; do -e "s#__VEHICLE_IDENTITY_MYSQL_USERNAME__#$VEHICLE_IDENTITY_MYSQL_USERNAME#g" \ -e "s#__VEHICLE_IDENTITY_MYSQL_PASSWORD__#$VEHICLE_IDENTITY_MYSQL_PASSWORD#g" \ -e "s#__VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL__#$VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL#g" \ + -e "s#__MYSQL_JDBC_URL__#$MYSQL_JDBC_URL#g" \ + -e "s#__MYSQL_USERNAME__#$MYSQL_USERNAME#g" \ + -e "s#__MYSQL_PASSWORD__#$MYSQL_PASSWORD#g" \ + -e "s#__YUTONG_MQTT_ENABLED__#$YUTONG_MQTT_ENABLED#g" \ + -e "s#__YUTONG_MQTT_URI__#$YUTONG_MQTT_URI#g" \ + -e "s#__YUTONG_MQTT_TOPIC__#$YUTONG_MQTT_TOPIC#g" \ + -e "s#__YUTONG_MQTT_USERNAME__#$YUTONG_MQTT_USERNAME#g" \ + -e "s#__YUTONG_MQTT_PASSWORD__#$YUTONG_MQTT_PASSWORD#g" \ "deploy/local/launchctl/com.lingniu.${service}.plist.template" \ > "$HOME/Library/LaunchAgents/com.lingniu.${service}.plist" done @@ -59,6 +77,8 @@ history 消费者会按协议和 raw/event 自动拆分 consumer group。设置 - `vehicle-history-live-gb32960-raw` - `vehicle-history-live-jt808-event` - `vehicle-history-live-jt808-raw` +- `vehicle-history-live-yutong-mqtt-event` +- `vehicle-history-live-yutong-mqtt-raw` 本地模板与 ECS 生产口径一致,身份绑定只使用 MySQL。启动前必须提供 `VEHICLE_IDENTITY_MYSQL_*` 连接参数;服务会自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表:前者只保存 `plate`、`vin`,后者保存注册字段;服务按刷新周期用注册表里的车牌关联 VIN,后续 raw/event 会带真实 VIN。 @@ -67,9 +87,11 @@ history 消费者会按协议和 raw/event 自动拆分 consumer group。设置 ```bash launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.gb32960.plist" launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.jt808.plist" +launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist" launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-history.plist" +launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist" -launchctl list | rg 'com.lingniu.(gb32960|jt808|vehicle-history)' +launchctl list | rg 'com.lingniu.(gb32960|jt808|yutong-mqtt|vehicle-history|vehicle-analytics)' ``` 替换 jar 前先停止服务,避免 KeepAlive 在 jar 拷贝中途拉起半包: @@ -77,7 +99,9 @@ launchctl list | rg 'com.lingniu.(gb32960|jt808|vehicle-history)' ```bash launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.gb32960.plist" || true launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.jt808.plist" || true +launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist" || true launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-history.plist" || true +launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist" || true ``` ## 验证地址 @@ -89,13 +113,21 @@ curl -sS http://127.0.0.1:20100/actuator/health/readiness curl -sS http://127.0.0.1:20400/actuator/health curl -sS http://127.0.0.1:20400/actuator/health/liveness curl -sS http://127.0.0.1:20400/actuator/health/readiness +curl -sS http://127.0.0.1:20500/actuator/health +curl -sS http://127.0.0.1:20500/actuator/health/liveness +curl -sS http://127.0.0.1:20500/actuator/health/readiness curl -sS http://127.0.0.1:20200/actuator/health curl -sS http://127.0.0.1:20200/actuator/health/liveness curl -sS http://127.0.0.1:20200/actuator/health/readiness +curl -sS http://127.0.0.1:20300/actuator/health +curl -sS http://127.0.0.1:20300/actuator/health/liveness +curl -sS http://127.0.0.1:20300/actuator/health/readiness ``` Swagger: - GB32960 ingest: `http://127.0.0.1:20100/swagger-ui/index.html` - JT808 ingest: `http://127.0.0.1:20400/swagger-ui/index.html` +- Yutong MQTT ingest: `http://127.0.0.1:20500/swagger-ui/index.html` - History query: `http://127.0.0.1:20200/swagger-ui/index.html` +- Analytics metrics: `http://127.0.0.1:20300/swagger-ui/index.html` diff --git a/deploy/local/launchctl/com.lingniu.vehicle-analytics.plist.template b/deploy/local/launchctl/com.lingniu.vehicle-analytics.plist.template new file mode 100644 index 00000000..1627d77c --- /dev/null +++ b/deploy/local/launchctl/com.lingniu.vehicle-analytics.plist.template @@ -0,0 +1,57 @@ + + + + + Label + com.lingniu.vehicle-analytics + WorkingDirectory + __PROJECT_ROOT__ + ProgramArguments + + __JAVA_BIN__ + -jar + __PROJECT_ROOT__/modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar + + EnvironmentVariables + + HTTP_PORT + 20300 + KAFKA_ENABLED + true + KAFKA_CONSUMER_ENABLED + true + KAFKA_CONSUMER_AUTO_OFFSET_RESET + latest + KAFKA_CONSUMER_CLIENT_ID_PREFIX + vehicle-analytics-live + KAFKA_BROKERS + 114.55.58.251:9092 + KAFKA_GROUP_STAT + vehicle-stat-live + VEHICLE_STAT_ENABLED + true + VEHICLE_STAT_ZONE_ID + Asia/Shanghai + VEHICLE_STAT_JT808_MILEAGE_ENABLED + true + MYSQL_JDBC_URL + __MYSQL_JDBC_URL__ + MYSQL_USERNAME + __MYSQL_USERNAME__ + MYSQL_PASSWORD + __MYSQL_PASSWORD__ + NACOS_CONFIG_ENABLED + false + MANAGEMENT_HEALTH_REDIS_ENABLED + false + + StandardOutPath + /tmp/lingniu-vehicle-analytics-live/vehicle-analytics.log + StandardErrorPath + /tmp/lingniu-vehicle-analytics-live/vehicle-analytics.err.log + RunAtLoad + + KeepAlive + + + diff --git a/deploy/local/launchctl/com.lingniu.vehicle-history.plist.template b/deploy/local/launchctl/com.lingniu.vehicle-history.plist.template index 5692dae0..d9555cf0 100644 --- a/deploy/local/launchctl/com.lingniu.vehicle-history.plist.template +++ b/deploy/local/launchctl/com.lingniu.vehicle-history.plist.template @@ -38,6 +38,10 @@ vehicle.event.jt808.v1 KAFKA_TOPIC_JT808_RAW vehicle.raw.jt808.v1 + KAFKA_TOPIC_YUTONG_MQTT_EVENT + vehicle.event.mqtt-yutong.v1 + KAFKA_TOPIC_YUTONG_MQTT_RAW + vehicle.raw.mqtt-yutong.v1 TDENGINE_HISTORY_ENABLED true TDENGINE_HISTORY_DATABASE @@ -64,9 +68,9 @@ false StandardOutPath - /tmp/lingniu-history-live/vehicle-history.log + /tmp/lingniu-vehicle-history-live/vehicle-history.log StandardErrorPath - /tmp/lingniu-history-live/vehicle-history.err.log + /tmp/lingniu-vehicle-history-live/vehicle-history.err.log RunAtLoad KeepAlive diff --git a/deploy/local/launchctl/com.lingniu.yutong-mqtt.plist.template b/deploy/local/launchctl/com.lingniu.yutong-mqtt.plist.template new file mode 100644 index 00000000..ce7e2a7b --- /dev/null +++ b/deploy/local/launchctl/com.lingniu.yutong-mqtt.plist.template @@ -0,0 +1,79 @@ + + + + + Label + com.lingniu.yutong-mqtt + WorkingDirectory + __PROJECT_ROOT__ + ProgramArguments + + __JAVA_BIN__ + -jar + __PROJECT_ROOT__/modules/apps/yutong-mqtt-app/target/yutong-mqtt-app.jar + + EnvironmentVariables + + HTTP_PORT + 20500 + YUTONG_MQTT_ENABLED + __YUTONG_MQTT_ENABLED__ + YUTONG_MQTT_AUTO_STARTUP + true + YUTONG_MQTT_ENDPOINT_NAME + yutong + YUTONG_MQTT_URI + __YUTONG_MQTT_URI__ + YUTONG_MQTT_TOPIC + __YUTONG_MQTT_TOPIC__ + YUTONG_MQTT_QOS + 1 + YUTONG_MQTT_CLIENT_ID + lingniu-yutong-mqtt-live + YUTONG_MQTT_USERNAME + __YUTONG_MQTT_USERNAME__ + YUTONG_MQTT_PASSWORD + __YUTONG_MQTT_PASSWORD__ + YUTONG_MQTT_CLEAN_SESSION + false + KAFKA_ENABLED + true + KAFKA_BROKERS + 114.55.58.251:9092 + KAFKA_NODE_ID + yutong-mqtt-live + KAFKA_TOPIC_YUTONG_MQTT_EVENT + vehicle.event.mqtt-yutong.v1 + KAFKA_TOPIC_YUTONG_MQTT_RAW + vehicle.raw.mqtt-yutong.v1 + KAFKA_TOPIC_YUTONG_MQTT_DLQ + vehicle.dlq.mqtt-yutong.v1 + SINK_ARCHIVE_PATH + __SHARED_ARCHIVE_PATH__ + VEHICLE_IDENTITY_STORE + mysql + VEHICLE_IDENTITY_MYSQL_TABLE + vehicle_identity_binding + VEHICLE_IDENTITY_MYSQL_JDBC_URL + __VEHICLE_IDENTITY_MYSQL_JDBC_URL__ + VEHICLE_IDENTITY_MYSQL_USERNAME + __VEHICLE_IDENTITY_MYSQL_USERNAME__ + VEHICLE_IDENTITY_MYSQL_PASSWORD + __VEHICLE_IDENTITY_MYSQL_PASSWORD__ + VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL + __VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL__ + NACOS_CONFIG_ENABLED + false + MANAGEMENT_HEALTH_REDIS_ENABLED + false + + StandardOutPath + /tmp/lingniu-yutong-mqtt-live/launchctl-yutong-mqtt.log + StandardErrorPath + /tmp/lingniu-yutong-mqtt-live/launchctl-yutong-mqtt.err.log + RunAtLoad + + KeepAlive + + + diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java index b3bbe922..ffac3144 100644 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java +++ b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java @@ -125,13 +125,37 @@ class PortainerComposeResourceLimitsTest { String readme = Files.readString(repositoryRoot() .resolve("deploy/local/launchctl/README.md")); - for (String httpPort : List.of("20100", "20400", "20200")) { + for (String httpPort : List.of("20100", "20400", "20500", "20200", "20300")) { assertThat(readme) .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/liveness") .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/readiness"); } } + @Test + void localLaunchctlTemplatesCoverActiveProductionApps() throws IOException { + Path launchctl = repositoryRoot().resolve("deploy/local/launchctl"); + String readme = Files.readString(launchctl.resolve("README.md")); + + assertThat(readme) + .contains("gb32960 jt808 yutong-mqtt vehicle-history vehicle-analytics") + .contains("mkdir -p /tmp/lingniu-gb32960-live /tmp/lingniu-jt808-live /tmp/lingniu-yutong-mqtt-live /tmp/lingniu-vehicle-history-live /tmp/lingniu-vehicle-analytics-live") + .contains("launchctl bootstrap \"gui/$(id -u)\" \"$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist\"") + .contains("launchctl bootstrap \"gui/$(id -u)\" \"$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist\"") + .contains("launchctl bootout \"gui/$(id -u)\" \"$HOME/Library/LaunchAgents/com.lingniu.yutong-mqtt.plist\" || true") + .contains("launchctl bootout \"gui/$(id -u)\" \"$HOME/Library/LaunchAgents/com.lingniu.vehicle-analytics.plist\" || true") + .contains("launchctl list | rg 'com.lingniu.(gb32960|jt808|yutong-mqtt|vehicle-history|vehicle-analytics)'") + .contains("Yutong MQTT ingest: `http://127.0.0.1:20500/swagger-ui/index.html`") + .contains("Analytics metrics: `http://127.0.0.1:20300/swagger-ui/index.html`") + .doesNotContain("三个服务"); + + assertLaunchctlTemplate(launchctl, "gb32960", "gb32960-ingest-app", "20100"); + assertLaunchctlTemplate(launchctl, "jt808", "jt808-ingest-app", "20400"); + assertLaunchctlTemplate(launchctl, "yutong-mqtt", "yutong-mqtt-app", "20500"); + assertLaunchctlTemplate(launchctl, "vehicle-history", "vehicle-history-app", "20200"); + assertLaunchctlTemplate(launchctl, "vehicle-analytics", "vehicle-analytics-app", "20300"); + } + @Test void tdengineVerificationRunbookChecksLivenessAndReadinessForManagedApps() throws IOException { String runbook = Files.readString(repositoryRoot() @@ -495,6 +519,21 @@ class PortainerComposeResourceLimitsTest { .doesNotContain(" start_period: 60s\n"); } + private static void assertLaunchctlTemplate(Path launchctl, + String serviceName, + String appName, + String httpPort) throws IOException { + Path template = launchctl.resolve("com.lingniu." + serviceName + ".plist.template"); + assertThat(template).exists(); + assertThat(Files.readString(template)) + .contains("com.lingniu." + serviceName + "") + .contains("__PROJECT_ROOT__/modules/apps/" + appName + "/target/" + appName + ".jar") + .contains("HTTP_PORT\n " + httpPort + "") + .contains("NACOS_CONFIG_ENABLED\n false") + .contains("MANAGEMENT_HEALTH_REDIS_ENABLED\n false") + .contains("/tmp/lingniu-" + serviceName + "-live/"); + } + private static String serviceBlock(String compose, String serviceName) { String marker = "\n " + serviceName + ":\n"; int start = compose.indexOf(marker);