perf(monitor): 监控配置加载缓存去重,避免重复全量反射扫描

- 新增 monitorConfigCache 进程内缓存(inflight 去重)与 carActionByTypeLookup
- opsMonitorConfig 支持传入预加载快照与缓存根,保存后失效缓存
- 运维配置页/选中信息面板复用缓存,修复车型动作勾选与 model 不同步

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-31 00:16:49 +08:00
co-authored by Cursor
parent 061acad9de
commit e6c49a0d78
5 changed files with 336 additions and 177 deletions
@@ -0,0 +1,19 @@
/** 按车型 key 查找已保存的动作白名单(兼容 fullTypeName / shortTypeName / 后缀匹配)。 */
export function lookupCarActionKeys(
map: Record<string, string[] | undefined> | null | undefined,
typeKey: string,
shortTypeName: string
): string[] {
if (!map) return []
const direct = map[typeKey] ?? map[shortTypeName]
if (direct?.length) return [...direct]
for (const [k, v] of Object.entries(map)) {
if (!v?.length) continue
if (k === typeKey || k === shortTypeName) return [...v]
if (typeKey.endsWith('.' + k) || k.endsWith('.' + shortTypeName)) return [...v]
if (typeKey.endsWith('.' + shortTypeName) && (k === shortTypeName || k.endsWith('.' + shortTypeName))) {
return [...v]
}
}
return []
}
@@ -0,0 +1,24 @@
import { reflectionApi, type MonitorConfigPayload } from '@/api/reflection'
/** 进程内缓存:避免每次选中车辆都 GET /monitor-config(全量反射扫描,极慢)。 */
let cached: MonitorConfigPayload | null = null
let inflight: Promise<MonitorConfigPayload> | null = null
export function invalidateMonitorConfigCache(): void {
cached = null
inflight = null
}
export async function fetchMonitorConfigCached(force = false): Promise<MonitorConfigPayload> {
if (!force && cached) return cached
if (!force && inflight) return inflight
inflight = reflectionApi.getMonitorConfig().then((mc) => {
cached = mc
inflight = null
return mc
}).catch((e) => {
inflight = null
throw e
})
return inflight
}
@@ -1,7 +1,15 @@
import { reflectionApi, emptyMonitorVisibilityMap, type MonitorVisibilityMap } from '@/api/reflection'
import {
reflectionApi,
emptyMonitorVisibilityMap,
type MonitorConfigPayload,
type MonitorVisibilityMap
} from '@/api/reflection'
import { invalidateMonitorConfigCache } from '@/utils/monitorConfigCache'
import { DEFAULT_OPS } from '@/mock/data/configs'
import type { MonitorOpsPolicy, OpsConfig } from '@/types/config'
export type MonitorConfigRoot = MonitorVisibilityMap & { carActionByType?: Record<string, string[]> }
export function defaultMonitorPolicy(): MonitorOpsPolicy {
return JSON.parse(JSON.stringify(DEFAULT_OPS.monitor)) as MonitorOpsPolicy
}
@@ -27,11 +35,22 @@ export function normalizeOpsConfig(raw?: Partial<OpsConfig> | null): OpsConfig {
}
}
type MonitorConfigRoot = MonitorVisibilityMap & { carActionByType?: Record<string, string[]> }
/** 深拷贝车型动作表,避免与 SimpleLite 返回对象共享引用导致勾选 UI 不同步。 */
export function cloneCarActionByType(
src?: Record<string, string[] | readonly string[]> | null
): Record<string, string[]> {
const out: Record<string, string[]> = {}
if (!src) return out
for (const [k, v] of Object.entries(src)) {
if (!k || !Array.isArray(v)) continue
out[k] = [...v]
}
return out
}
function pickMonitorRoot(cfg: MonitorConfigRoot) {
return {
carActionByType: { ...(cfg.carActionByType ?? {}) },
carActionByType: cloneCarActionByType(cfg.carActionByType),
siteActionKeys: [...(cfg.site?.methods ?? [])],
trackActionKeys: [...(cfg.track?.methods ?? [])]
}
@@ -41,35 +60,56 @@ function pickMonitorRoot(cfg: MonitorConfigRoot) {
* 从 SimpleLite simple.json 加载地图监控配置(运行时唯一可信源)。
* Platform ops 里的 monitor 仅作备份;以 SimpleLite 为准覆盖表单。
*/
export async function loadMonitorSettingsIntoOps(payload: OpsConfig): Promise<OpsConfig> {
export interface LoadMonitorSettingsResult {
config: OpsConfig
/** 与 SimpleLite 对齐的 monitor-config 快照,供保存时合并 fields/status,避免每次 POST 前再 GET。 */
cache: MonitorConfigRoot | null
}
export async function loadMonitorSettingsIntoOps(
payload: OpsConfig,
preloaded?: MonitorConfigPayload | null
): Promise<LoadMonitorSettingsResult> {
const next = normalizeOpsConfig(payload)
let cache: MonitorConfigRoot | null = null
try {
const mc = await reflectionApi.getMonitorConfig()
const picked = pickMonitorRoot(mc.config as MonitorConfigRoot)
const mc = preloaded ?? (await reflectionApi.getMonitorConfig())
cache = mc.config as MonitorConfigRoot
const picked = pickMonitorRoot(cache)
next.monitor.carActionByType = picked.carActionByType
next.monitor.site.actionKeys = picked.siteActionKeys
next.monitor.track.actionKeys = picked.trackActionKeys
} catch {
// SimpleLite 未连接:退回 Platform payload 中已有的 monitor(若有)
}
return next
return { config: next, cache }
}
/**
* 将运营维护表单中的地图监控配置写入 SimpleLitesimple.json)。
* 保留「地图监控配置」页面对 fields/status 的既有设置,只更新动作相关字段。
*/
export async function saveMonitorSettingsFromOps(payload: OpsConfig): Promise<void> {
export interface SaveMonitorSettingsOptions {
/** 页面加载时缓存的 monitor-config,避免勾选自动保存时重复 GET(该接口会全量反射扫描)。 */
cachedRoot?: MonitorConfigRoot | null
}
export async function saveMonitorSettingsFromOps(
payload: OpsConfig,
options?: SaveMonitorSettingsOptions
): Promise<MonitorConfigRoot> {
const monitor = normalizeOpsConfig(payload).monitor
let current: MonitorConfigRoot = emptyMonitorVisibilityMap()
try {
const mc = await reflectionApi.getMonitorConfig()
current = mc.config as MonitorConfigRoot
} catch {
// 无现有配置时用空壳,仍可写入动作白名单
let current: MonitorConfigRoot = options?.cachedRoot ?? emptyMonitorVisibilityMap()
if (!options?.cachedRoot) {
try {
const mc = await reflectionApi.getMonitorConfig()
current = mc.config as MonitorConfigRoot
} catch {
// 无现有配置时用空壳,仍可写入动作白名单
}
}
await reflectionApi.saveMonitorConfig({
const saved = await reflectionApi.saveMonitorConfig({
car: {
fields: [...(current.car?.fields ?? [])],
status: [...(current.car?.status ?? [])],
@@ -85,8 +125,10 @@ export async function saveMonitorSettingsFromOps(payload: OpsConfig): Promise<vo
status: [...(current.track?.status ?? [])],
methods: [...monitor.track.actionKeys]
},
carActionByType: { ...monitor.carActionByType }
carActionByType: cloneCarActionByType(monitor.carActionByType)
})
invalidateMonitorConfigCache()
return saved as MonitorConfigRoot
}
/** @deprecated 使用 loadMonitorSettingsIntoOps */