feat(platform): expose deployed release
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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()}`,
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user