feat(platform): expose deployed release

This commit is contained in:
lingniu
2026-07-04 15:34:37 +08:00
parent 1f069178d6
commit ff36cb4bd1
9 changed files with 32 additions and 1 deletions

View File

@@ -57,6 +57,7 @@ func NewServer(cfg config.Config) http.Handler {
AMapSecurityProxyEnabled: strings.TrimSpace(cfg.AMapSecurityCode) != "" && strings.TrimSpace(cfg.AMapServiceHost) != "", AMapSecurityProxyEnabled: strings.TrimSpace(cfg.AMapSecurityCode) != "" && strings.TrimSpace(cfg.AMapServiceHost) != "",
AMapSecurityCodeExposed: exposedAMapSecurityCode(cfg) != "", AMapSecurityCodeExposed: exposedAMapSecurityCode(cfg) != "",
AMapSecurityServiceHost: strings.TrimSpace(cfg.AMapServiceHost), AMapSecurityServiceHost: strings.TrimSpace(cfg.AMapServiceHost),
PlatformRelease: strings.TrimSpace(cfg.PlatformRelease),
})) }))
handler := static.Handler(cfg.StaticDir, api) handler := static.Handler(cfg.StaticDir, api)
handler = withAppConfig(handler, cfg) handler = withAppConfig(handler, cfg)

View File

@@ -23,6 +23,7 @@ type Config struct {
AMapWebJSKey string AMapWebJSKey string
AMapSecurityCode string AMapSecurityCode string
AMapServiceHost string AMapServiceHost string
PlatformRelease string
} }
func Load() Config { func Load() Config {
@@ -43,6 +44,7 @@ func Load() Config {
AMapWebJSKey: os.Getenv("AMAP_WEB_JS_KEY"), AMapWebJSKey: os.Getenv("AMAP_WEB_JS_KEY"),
AMapSecurityCode: os.Getenv("AMAP_SECURITY_JS_CODE"), AMapSecurityCode: os.Getenv("AMAP_SECURITY_JS_CODE"),
AMapServiceHost: os.Getenv("AMAP_SECURITY_SERVICE_HOST"), AMapServiceHost: os.Getenv("AMAP_SECURITY_SERVICE_HOST"),
PlatformRelease: os.Getenv("PLATFORM_RELEASE"),
} }
} }

View File

@@ -14,3 +14,13 @@ func TestLoadReadsRequestTimeout(t *testing.T) {
t.Fatalf("RequestTimeout = %s, want 1.2s", cfg.RequestTimeout) 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)
}
}

View File

@@ -895,6 +895,7 @@ func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) {
AMapSecurityProxyEnabled: true, AMapSecurityProxyEnabled: true,
AMapSecurityCodeExposed: false, AMapSecurityCodeExposed: false,
AMapSecurityServiceHost: "/_AMapService", AMapSecurityServiceHost: "/_AMapService",
PlatformRelease: "platform-20260704153005",
})) }))
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil) req := httptest.NewRequest(http.MethodGet, "/api/ops/health", nil)
@@ -920,6 +921,9 @@ func TestHandlerOpsHealthIncludesVehicleServiceRuntime(t *testing.T) {
if body.Data.Runtime.AMapSecurityServiceHost != "/_AMapService" { if body.Data.Runtime.AMapSecurityServiceHost != "/_AMapService" {
t.Fatalf("ops health should include AMap security service host, got %+v", body.Data.Runtime) 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) { func TestHandlerVehicleDataAPIsResolveVehicleKeyword(t *testing.T) {

View File

@@ -349,4 +349,5 @@ type RuntimeInfo struct {
AMapSecurityProxyEnabled bool `json:"amapSecurityProxyEnabled"` AMapSecurityProxyEnabled bool `json:"amapSecurityProxyEnabled"`
AMapSecurityCodeExposed bool `json:"amapSecurityCodeExposed"` AMapSecurityCodeExposed bool `json:"amapSecurityCodeExposed"`
AMapSecurityServiceHost string `json:"amapSecurityServiceHost"` AMapSecurityServiceHost string `json:"amapSecurityServiceHost"`
PlatformRelease string `json:"platformRelease"`
} }

View File

@@ -338,6 +338,7 @@ export interface RuntimeInfo {
amapSecurityProxyEnabled?: boolean; amapSecurityProxyEnabled?: boolean;
amapSecurityCodeExposed?: boolean; amapSecurityCodeExposed?: boolean;
amapSecurityServiceHost?: string; amapSecurityServiceHost?: string;
platformRelease?: string;
} }
export interface Page<T> { export interface Page<T> {

View File

@@ -294,6 +294,12 @@ export function Realtime({
color: amapSecurityCodeExposed ? 'red' as const : 'green' as const, color: amapSecurityCodeExposed ? 'red' as const : 'green' as const,
detail: amapSecurityCodeExposed ? '当前安全码会下发到浏览器,只建议调试使用。' : '生产安全码保持在服务端环境变量中。' detail: amapSecurityCodeExposed ? '当前安全码会下发到浏览器,只建议调试使用。' : '生产安全码保持在服务端环境变量中。'
}, },
{
label: '当前版本',
value: runtime?.platformRelease || '未标记',
color: runtime?.platformRelease ? 'blue' as const : 'grey' as const,
detail: runtime?.platformRelease ? '当前 ECS 正在运行的车辆中台 release。' : '未注入 PLATFORM_RELEASE部署追踪会降级。'
},
{ {
label: '定位覆盖', label: '定位覆盖',
value: `${locatedCount.toLocaleString()} / ${rows.length.toLocaleString()}`, value: `${locatedCount.toLocaleString()} / ${rows.length.toLocaleString()}`,

View File

@@ -7476,7 +7476,8 @@ test('shows production AMap integration status on realtime page', async () => {
amapWebJsConfigured: true, amapWebJsConfigured: true,
amapSecurityProxyEnabled: true, amapSecurityProxyEnabled: true,
amapSecurityCodeExposed: false, amapSecurityCodeExposed: false,
amapSecurityServiceHost: '/_AMapService' amapSecurityServiceHost: '/_AMapService',
platformRelease: 'platform-20260704153005'
} }
}, },
traceId: 'trace-test', 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.getAllByText('已配置').length).toBeGreaterThan(0);
expect(screen.getByText('安全代理')).toBeInTheDocument(); expect(screen.getByText('安全代理')).toBeInTheDocument();
expect(screen.getByText('/_AMapService')).toBeInTheDocument(); expect(screen.getByText('/_AMapService')).toBeInTheDocument();
expect(screen.getByText('当前版本')).toBeInTheDocument();
expect(screen.getByText('platform-20260704153005')).toBeInTheDocument();
expect(screen.getByText('定位覆盖')).toBeInTheDocument(); expect(screen.getByText('定位覆盖')).toBeInTheDocument();
expect(screen.getByText('1 / 2')).toBeInTheDocument(); expect(screen.getByText('1 / 2')).toBeInTheDocument();
}); });

View File

@@ -39,10 +39,13 @@ REQUEST_TIMEOUT_MS=5000
AMAP_WEB_JS_KEY=*** AMAP_WEB_JS_KEY=***
AMAP_SECURITY_JS_CODE=*** AMAP_SECURITY_JS_CODE=***
AMAP_SECURITY_SERVICE_HOST=/_AMapService 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. `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 ## Health
```bash ```bash