perf: 实时监控性能优化

- 后端:车辆关联信息缓存5分钟、两库并行查询、支持服务端
  筛选/排序/分页(默认返回100条)
- 前端:筛选和排序参数传给后端,不再加载全量数据
- 筛选选项(部门/客户/车牌)仅首次加载获取

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-01 22:11:52 +08:00
parent ad17803ed1
commit 1fb9d53873
4 changed files with 125 additions and 60 deletions

View File

@@ -18,41 +18,64 @@ LEFT JOIN tab_user u ON u.id = c.bd AND u.is_deleted = 0
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
WHERE truck.is_deleted = 0 AND truck.is_operation = 1`;
// 车辆关联信息缓存5分钟过期
let vehicleInfoCache: { data: Map<string, any>; expiry: number } | null = null;
const CACHE_TTL = 5 * 60 * 1000;
async function getVehicleInfoMap(): Promise<Map<string, any>> {
const now = Date.now();
if (vehicleInfoCache && now < vehicleInfoCache.expiry) {
return vehicleInfoCache.data;
}
const [rows] = await pool.execute(VEHICLE_INFO_SQL) as any;
const map = new Map<string, any>();
for (const row of rows) {
map.set(row.plate, row);
}
vehicleInfoCache = { data: map, expiry: now + CACHE_TTL };
return map;
}
// GET /monitoring — 实时监控数据
app.get('/monitoring', async (c) => {
try {
// 1. 从 hydrogen_energy 取最新日期的里程数据
const [dateRows] = await mileagePool.execute(
'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats'
) as any;
const latestDate = dateRows[0]?.latest;
if (!latestDate) return c.json({ vehicles: [], updatedAt: new Date().toISOString() });
const sortBy = c.req.query('sortBy') || 'today';
const sortOrder = c.req.query('sortOrder') || 'desc';
const limit = Number(c.req.query('limit')) || 100;
const offset = Number(c.req.query('offset')) || 0;
const search = c.req.query('search') || '';
const dept = c.req.query('dept') || '';
const customer = c.req.query('customer') || '';
const [mileageRows] = await mileagePool.execute(
`SELECT plate, vin, daily_km, total_km, source
FROM v_vehicle_daily_stats
WHERE stat_date = ?`,
[latestDate]
) as any;
// 并行查询两个数据库
const [mileageResult, infoMap] = await Promise.all([
(async () => {
const [dateRows] = await mileagePool.execute(
'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats'
) as any;
const latestDate = dateRows[0]?.latest;
if (!latestDate) return [];
const [rows] = await mileagePool.execute(
`SELECT plate, vin, daily_km, total_km, source
FROM v_vehicle_daily_stats WHERE stat_date = ?`,
[latestDate]
) as any;
return rows;
})(),
getVehicleInfoMap(),
]);
// 对于同一 plate 可能有多条记录(不同 source取 daily_km 最大的
// 去重:同一 plate 取 daily_km 最大的
const mileageMap = new Map<string, any>();
for (const row of mileageRows) {
for (const row of mileageResult) {
const existing = mileageMap.get(row.plate);
if (!existing || Number(row.daily_km) > Number(existing.daily_km)) {
mileageMap.set(row.plate, row);
}
}
// 2. 从 lingniu_prod 取车辆关联信息
const [infoRows] = await pool.execute(VEHICLE_INFO_SQL) as any;
const infoMap = new Map<string, any>();
for (const row of infoRows) {
infoMap.set(row.plate, row);
}
// 3. 合并
const vehicles = Array.from(mileageMap.values()).map((m: any) => {
// 合并 + 筛选
let vehicles = Array.from(mileageMap.values()).map((m: any) => {
const info = infoMap.get(m.plate);
const dailyKm = Number(m.daily_km) || 0;
const source = m.source || 'NONE';
@@ -70,10 +93,33 @@ app.get('/monitoring', async (c) => {
};
});
return c.json({ vehicles, updatedAt: new Date().toISOString() });
// 服务端筛选
if (search) {
const q = search.toLowerCase();
vehicles = vehicles.filter(v =>
v.plate.toLowerCase().includes(q) ||
(v.customer || '').toLowerCase().includes(q)
);
}
if (dept) vehicles = vehicles.filter(v => v.department === dept);
if (customer) vehicles = vehicles.filter(v => v.customer === customer);
const total = vehicles.length;
// 服务端排序
vehicles.sort((a, b) => {
const valA = sortBy === 'today' ? a.dailyKm : (a.totalKm || 0);
const valB = sortBy === 'today' ? b.dailyKm : (b.totalKm || 0);
return sortOrder === 'desc' ? valB - valA : valA - valB;
});
// 分页
const paged = vehicles.slice(offset, offset + limit);
return c.json({ vehicles: paged, total, updatedAt: new Date().toISOString() });
} catch (e) {
console.error('monitoring error:', e);
return c.json({ vehicles: [], updatedAt: new Date().toISOString() }, 500);
return c.json({ vehicles: [], total: 0, updatedAt: new Date().toISOString() }, 500);
}
});