From ff36cb4bd1c9ba37e515bcaf6c4ebf630202d97d Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 15:34:37 +0800 Subject: [PATCH] feat(platform): expose deployed release --- vehicle-data-platform/apps/api/internal/app/server.go | 1 + .../apps/api/internal/config/config.go | 2 ++ .../apps/api/internal/config/config_test.go | 10 ++++++++++ .../apps/api/internal/platform/handler_test.go | 4 ++++ .../apps/api/internal/platform/model.go | 1 + vehicle-data-platform/apps/web/src/api/types.ts | 1 + vehicle-data-platform/apps/web/src/pages/Realtime.tsx | 6 ++++++ vehicle-data-platform/apps/web/src/test/App.test.tsx | 5 ++++- vehicle-data-platform/docs/deployment.md | 3 +++ 9 files changed, 32 insertions(+), 1 deletion(-) diff --git a/vehicle-data-platform/apps/api/internal/app/server.go b/vehicle-data-platform/apps/api/internal/app/server.go index 745e0398..43f6dddd 100644 --- a/vehicle-data-platform/apps/api/internal/app/server.go +++ b/vehicle-data-platform/apps/api/internal/app/server.go @@ -57,6 +57,7 @@ func NewServer(cfg config.Config) http.Handler { AMapSecurityProxyEnabled: strings.TrimSpace(cfg.AMapSecurityCode) != "" && strings.TrimSpace(cfg.AMapServiceHost) != "", AMapSecurityCodeExposed: exposedAMapSecurityCode(cfg) != "", AMapSecurityServiceHost: strings.TrimSpace(cfg.AMapServiceHost), + PlatformRelease: strings.TrimSpace(cfg.PlatformRelease), })) handler := static.Handler(cfg.StaticDir, api) handler = withAppConfig(handler, cfg) diff --git a/vehicle-data-platform/apps/api/internal/config/config.go b/vehicle-data-platform/apps/api/internal/config/config.go index f2756cda..99aadfde 100644 --- a/vehicle-data-platform/apps/api/internal/config/config.go +++ b/vehicle-data-platform/apps/api/internal/config/config.go @@ -23,6 +23,7 @@ type Config struct { AMapWebJSKey string AMapSecurityCode string AMapServiceHost string + PlatformRelease string } func Load() Config { @@ -43,6 +44,7 @@ func Load() Config { AMapWebJSKey: os.Getenv("AMAP_WEB_JS_KEY"), AMapSecurityCode: os.Getenv("AMAP_SECURITY_JS_CODE"), AMapServiceHost: os.Getenv("AMAP_SECURITY_SERVICE_HOST"), + PlatformRelease: os.Getenv("PLATFORM_RELEASE"), } } diff --git a/vehicle-data-platform/apps/api/internal/config/config_test.go b/vehicle-data-platform/apps/api/internal/config/config_test.go index afc8e14e..070aabaf 100644 --- a/vehicle-data-platform/apps/api/internal/config/config_test.go +++ b/vehicle-data-platform/apps/api/internal/config/config_test.go @@ -14,3 +14,13 @@ func TestLoadReadsRequestTimeout(t *testing.T) { t.Fatalf("RequestTimeout = %s, want 1.2s", cfg.RequestTimeout) } } + +func TestLoadReadsPlatformRelease(t *testing.T) { + t.Setenv("PLATFORM_RELEASE", "platform-20260704153005") + + cfg := Load() + + if cfg.PlatformRelease != "platform-20260704153005" { + t.Fatalf("PlatformRelease = %q", cfg.PlatformRelease) + } +} diff --git a/vehicle-data-platform/apps/api/internal/platform/handler_test.go b/vehicle-data-platform/apps/api/internal/platform/handler_test.go index 08f0d5ac..8298c4ab 100644 --- a/vehicle-data-platform/apps/api/internal/platform/handler_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/handler_test.go @@ -895,6 +895,7 @@ func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) { AMapSecurityProxyEnabled: true, AMapSecurityCodeExposed: false, AMapSecurityServiceHost: "/_AMapService", + PlatformRelease: "platform-20260704153005", })) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil) @@ -920,6 +921,9 @@ func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) { if body.Data.Runtime.AMapSecurityServiceHost != "/_AMapService" { t.Fatalf("ops health should include AMap security service host, got %+v", body.Data.Runtime) } + if body.Data.Runtime.PlatformRelease != "platform-20260704153005" { + t.Fatalf("ops health should include platform release, got %+v", body.Data.Runtime) + } } func TestHandlerVehicleDataAPIsResolveVehicleKeyword(t *testing.T) { diff --git a/vehicle-data-platform/apps/api/internal/platform/model.go b/vehicle-data-platform/apps/api/internal/platform/model.go index 4c8c642b..5ae53904 100644 --- a/vehicle-data-platform/apps/api/internal/platform/model.go +++ b/vehicle-data-platform/apps/api/internal/platform/model.go @@ -349,4 +349,5 @@ type RuntimeInfo struct { AMapSecurityProxyEnabled bool `json:"amapSecurityProxyEnabled"` AMapSecurityCodeExposed bool `json:"amapSecurityCodeExposed"` AMapSecurityServiceHost string `json:"amapSecurityServiceHost"` + PlatformRelease string `json:"platformRelease"` } diff --git a/vehicle-data-platform/apps/web/src/api/types.ts b/vehicle-data-platform/apps/web/src/api/types.ts index 406d6582..1886f803 100644 --- a/vehicle-data-platform/apps/web/src/api/types.ts +++ b/vehicle-data-platform/apps/web/src/api/types.ts @@ -338,6 +338,7 @@ export interface RuntimeInfo { amapSecurityProxyEnabled?: boolean; amapSecurityCodeExposed?: boolean; amapSecurityServiceHost?: string; + platformRelease?: string; } export interface Page { diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index fdb74bbb..c862d86c 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -294,6 +294,12 @@ export function Realtime({ color: amapSecurityCodeExposed ? 'red' as const : 'green' as const, detail: amapSecurityCodeExposed ? '当前安全码会下发到浏览器,只建议调试使用。' : '生产安全码保持在服务端环境变量中。' }, + { + label: '当前版本', + value: runtime?.platformRelease || '未标记', + color: runtime?.platformRelease ? 'blue' as const : 'grey' as const, + detail: runtime?.platformRelease ? '当前 ECS 正在运行的车辆中台 release。' : '未注入 PLATFORM_RELEASE,部署追踪会降级。' + }, { label: '定位覆盖', value: `${locatedCount.toLocaleString()} / ${rows.length.toLocaleString()}`, diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index 7229d543..794fc74b 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -7476,7 +7476,8 @@ test('shows production AMap integration status on realtime page', async () => { amapWebJsConfigured: true, amapSecurityProxyEnabled: true, amapSecurityCodeExposed: false, - amapSecurityServiceHost: '/_AMapService' + amapSecurityServiceHost: '/_AMapService', + platformRelease: 'platform-20260704153005' } }, traceId: 'trace-test', @@ -7557,6 +7558,8 @@ test('shows production AMap integration status on realtime page', async () => { expect(screen.getAllByText('已配置').length).toBeGreaterThan(0); expect(screen.getByText('安全代理')).toBeInTheDocument(); expect(screen.getByText('/_AMapService')).toBeInTheDocument(); + expect(screen.getByText('当前版本')).toBeInTheDocument(); + expect(screen.getByText('platform-20260704153005')).toBeInTheDocument(); expect(screen.getByText('定位覆盖')).toBeInTheDocument(); expect(screen.getByText('1 / 2')).toBeInTheDocument(); }); diff --git a/vehicle-data-platform/docs/deployment.md b/vehicle-data-platform/docs/deployment.md index de9c7b0f..853fdee3 100644 --- a/vehicle-data-platform/docs/deployment.md +++ b/vehicle-data-platform/docs/deployment.md @@ -39,10 +39,13 @@ REQUEST_TIMEOUT_MS=5000 AMAP_WEB_JS_KEY=*** AMAP_SECURITY_JS_CODE=*** AMAP_SECURITY_SERVICE_HOST=/_AMapService +PLATFORM_RELEASE=platform-YYYYMMDDHHMMSS ``` `AMAP_WEB_JS_KEY` is served to the browser through `/app-config.js` so the same static build can be reused across environments. For production, set both `AMAP_SECURITY_JS_CODE` and `AMAP_SECURITY_SERVICE_HOST=/_AMapService`; the API service keeps the security code on the server and proxies AMap requests with `jscode` appended. Only omit `AMAP_SECURITY_SERVICE_HOST` for controlled debugging where exposing `AMAP_SECURITY_JS_CODE` to the browser is acceptable. +`PLATFORM_RELEASE` is surfaced by `/api/ops/health.runtime.platformRelease` so operators can confirm which ECS release is currently active after a deployment. + ## Health ```bash