diff --git a/vehicle-data-platform/apps/api/internal/app/server.go b/vehicle-data-platform/apps/api/internal/app/server.go index 105801d0..745e0398 100644 --- a/vehicle-data-platform/apps/api/internal/app/server.go +++ b/vehicle-data-platform/apps/api/internal/app/server.go @@ -52,7 +52,11 @@ func NewServer(cfg config.Config) http.Handler { } } api := platform.NewHandler(platform.NewServiceWithRuntime(store, platform.RuntimeInfo{ - RequestTimeoutMs: int(cfg.RequestTimeout / time.Millisecond), + RequestTimeoutMs: int(cfg.RequestTimeout / time.Millisecond), + AMapWebJSConfigured: strings.TrimSpace(cfg.AMapWebJSKey) != "", + AMapSecurityProxyEnabled: strings.TrimSpace(cfg.AMapSecurityCode) != "" && strings.TrimSpace(cfg.AMapServiceHost) != "", + AMapSecurityCodeExposed: exposedAMapSecurityCode(cfg) != "", + AMapSecurityServiceHost: strings.TrimSpace(cfg.AMapServiceHost), })) handler := static.Handler(cfg.StaticDir, api) handler = withAppConfig(handler, cfg) 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 2467b069..08f0d5ac 100644 --- a/vehicle-data-platform/apps/api/internal/platform/handler_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/handler_test.go @@ -889,7 +889,13 @@ func TestHandlerQualityNotificationPlan(t *testing.T) { } func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) { - handler := NewHandler(NewServiceWithRuntime(NewMockStore(), RuntimeInfo{RequestTimeoutMs: 1500})) + handler := NewHandler(NewServiceWithRuntime(NewMockStore(), RuntimeInfo{ + RequestTimeoutMs: 1500, + AMapWebJSConfigured: true, + AMapSecurityProxyEnabled: true, + AMapSecurityCodeExposed: false, + AMapSecurityServiceHost: "/_AMapService", + })) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil) handler.ServeHTTP(rec, req) @@ -905,6 +911,15 @@ func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) { if body.Data.Runtime.RequestTimeoutMs != 1500 { t.Fatalf("ops health should include request timeout runtime, got %+v body=%s", body.Data.Runtime, rec.Body.String()) } + if !body.Data.Runtime.AMapWebJSConfigured || !body.Data.Runtime.AMapSecurityProxyEnabled { + t.Fatalf("ops health should expose AMap runtime readiness, got %+v body=%s", body.Data.Runtime, rec.Body.String()) + } + if body.Data.Runtime.AMapSecurityCodeExposed { + t.Fatalf("ops health should show that AMap security code is not exposed when proxy is enabled: %+v", body.Data.Runtime) + } + if body.Data.Runtime.AMapSecurityServiceHost != "/_AMapService" { + t.Fatalf("ops health should include AMap security service host, 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 e05d4aae..4c8c642b 100644 --- a/vehicle-data-platform/apps/api/internal/platform/model.go +++ b/vehicle-data-platform/apps/api/internal/platform/model.go @@ -344,5 +344,9 @@ type OpsHealth struct { } type RuntimeInfo struct { - RequestTimeoutMs int `json:"requestTimeoutMs"` + RequestTimeoutMs int `json:"requestTimeoutMs"` + AMapWebJSConfigured bool `json:"amapWebJsConfigured"` + AMapSecurityProxyEnabled bool `json:"amapSecurityProxyEnabled"` + AMapSecurityCodeExposed bool `json:"amapSecurityCodeExposed"` + AMapSecurityServiceHost string `json:"amapSecurityServiceHost"` } diff --git a/vehicle-data-platform/apps/web/src/api/types.ts b/vehicle-data-platform/apps/web/src/api/types.ts index b4fa8407..406d6582 100644 --- a/vehicle-data-platform/apps/web/src/api/types.ts +++ b/vehicle-data-platform/apps/web/src/api/types.ts @@ -334,6 +334,10 @@ export interface OpsHealth { export interface RuntimeInfo { requestTimeoutMs: number; + amapWebJsConfigured?: boolean; + amapSecurityProxyEnabled?: boolean; + amapSecurityCodeExposed?: boolean; + amapSecurityServiceHost?: 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 665d16ed..fdb74bbb 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -2,7 +2,7 @@ import { Button, Card, Form, Select, Space, Table, Tabs, Tag, Toast, Typography import { IconCopy } from '@douyinfe/semi-icons'; import { useEffect, useState } from 'react'; import { api } from '../api/client'; -import type { VehicleRealtimeRow } from '../api/types'; +import type { OpsHealth, VehicleRealtimeRow } from '../api/types'; import { DataEmpty } from '../components/DataEmpty'; import { PageHeader } from '../components/PageHeader'; import { SourceStatusTags } from '../components/SourceStatusTags'; @@ -190,8 +190,13 @@ export function Realtime({ const [loading, setLoading] = useState(true); const [filters, setFilters] = useState>(initialFilters); const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 50, total: 0 }); + const [opsHealth, setOpsHealth] = useState(null); const amapConfig = getAMapConfig(); - const amapConfigured = isAMapConfigured(amapConfig); + const runtime = opsHealth?.runtime; + const amapConfigured = runtime?.amapWebJsConfigured ?? isAMapConfigured(amapConfig); + const amapSecurityServiceHost = runtime?.amapSecurityServiceHost || amapConfig.securityServiceHost; + const amapSecurityProxyEnabled = runtime?.amapSecurityProxyEnabled ?? Boolean(amapSecurityServiceHost); + const amapSecurityCodeExposed = runtime?.amapSecurityCodeExposed ?? Boolean(amapConfig.securityJsCode && !amapSecurityServiceHost); const load = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { setLoading(true); @@ -212,6 +217,9 @@ export function Realtime({ useEffect(() => { setFilters(initialFilters); load(initialFilters, 1, pagination.pageSize); + api.opsHealth() + .then(setOpsHealth) + .catch(() => setOpsHealth(null)); }, [JSON.stringify(initialFilters)]); const applyFilters = (nextFilters: Record) => { @@ -276,9 +284,15 @@ export function Realtime({ }, { label: '安全代理', - value: amapConfig.securityServiceHost || '未启用', - color: amapConfig.securityServiceHost ? 'green' as const : 'grey' as const, - detail: amapConfig.securityServiceHost ? '安全密钥由服务端追加,不下发到浏览器。' : '未配置代理时会退回前端安全码模式。' + value: amapSecurityServiceHost || '未启用', + color: amapSecurityProxyEnabled ? 'green' as const : 'grey' as const, + detail: amapSecurityProxyEnabled ? '安全密钥由服务端追加,不下发到浏览器。' : '未配置代理时会退回前端安全码模式。' + }, + { + label: '安全码', + value: amapSecurityCodeExposed ? '已暴露' : '未暴露', + color: amapSecurityCodeExposed ? 'red' as const : 'green' as const, + detail: amapSecurityCodeExposed ? '当前安全码会下发到浏览器,只建议调试使用。' : '生产安全码保持在服务端环境变量中。' }, { label: '定位覆盖', 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 7e1935f2..7229d543 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -7457,7 +7457,7 @@ test('shows production AMap integration status on realtime page', async () => { window.history.replaceState(null, '', '/#/realtime'); Object.defineProperty(window, '__LINGNIU_APP_CONFIG__', { configurable: true, - value: { amapWebJsKey: 'amap-key', amapSecurityServiceHost: '/_AMapService' } + value: {} }); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); @@ -7465,7 +7465,20 @@ test('shows production AMap integration status on realtime page', async () => { return { ok: true, json: async () => ({ - data: { linkHealth: [], kafkaLag: 0, redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + data: { + linkHealth: [], + kafkaLag: 0, + redisOnlineKeys: 0, + tdengineWritable: true, + mysqlWritable: true, + runtime: { + requestTimeoutMs: 5000, + amapWebJsConfigured: true, + amapSecurityProxyEnabled: true, + amapSecurityCodeExposed: false, + amapSecurityServiceHost: '/_AMapService' + } + }, traceId: 'trace-test', timestamp: 1783094400000 })