perf(energy): SWR 缓存 + 自调度刷新,氢能总览 6s → 13ms
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

接口侧:
- cache.ts 改为 stale-while-revalidate:每个 key 自调度,TTL 到期前 5s 后台刷新,用户永远命中热缓存
- 闲置 10 分钟后停止调度,避免空跑
- loader 失败保留旧值 + 10s 后退避重试
- 所有 4 个端点支持 ?force=1 强制绕过缓存

前端 HydrogenOverview:
- 顶部加 RefreshCw 按钮(强刷绕过缓存),带旋转动画
- 显示"更新于 X 秒前"相对时间
- 刷新中:顶部 0.5px 流光进度条,不替换内容、不闪烁
- 60s 静默自动刷新(命中后端热缓存)

实测:cold 6.1s → 命中 13ms(470× 提速)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-30 17:43:24 +08:00
parent 6ad4b5e2a4
commit f06b0d21eb
4 changed files with 211 additions and 47 deletions

View File

@@ -61,6 +61,7 @@ function enumerateDates(range: Range): string[] {
// =========================================================
app.get('/hydrogen/overview', async (c) => {
const yearParam = c.req.query('year');
const force = c.req.query('force') === '1';
const today = new Date();
const todayYear = today.getFullYear();
const requestedYear = yearParam ? Number(yearParam) || todayYear : todayYear;
@@ -303,7 +304,7 @@ app.get('/hydrogen/overview', async (c) => {
}));
return { kpi, top5, regions, monthly, customers, stations, availableYears, year };
});
}, { force });
return c.json(data);
});
@@ -313,6 +314,7 @@ app.get('/hydrogen/overview', async (c) => {
app.get('/hydrogen/daily', async (c) => {
const range = (c.req.query('range') || 'last15') as Range;
const customer = (c.req.query('customer') || 'external') as CustomerKind;
const force = c.req.query('force') === '1';
const data = await cached(`hydrogen/daily?range=${range}&customer=${customer}`, async () => {
@@ -423,7 +425,7 @@ app.get('/hydrogen/daily', async (c) => {
// 按日期降序返回
const result = ascDays.slice().sort((a, b) => b.date.localeCompare(a.date));
return result;
});
}, { force });
return c.json(data);
});
@@ -431,6 +433,7 @@ app.get('/hydrogen/daily', async (c) => {
// 电能 总览KPI + 本月每日柱图数据 —— 数据源bi_ele_charge_record
// =========================================================
app.get('/electric/overview', async (c) => {
const force = c.req.query('force') === '1';
const data = await cached('electric/overview', async () => {
const [kpiRows] = await pool.query<RowDataPacket[]>(
`SELECT
@@ -504,7 +507,7 @@ app.get('/electric/overview', async (c) => {
kpi: { totalKwh, totalFee, monthKwh, monthFee, todayKwh, todayFee, todayChainPct },
trend: trendArr,
};
});
}, { force });
return c.json(data);
});
@@ -516,6 +519,7 @@ app.get('/electric/overview', async (c) => {
app.get('/electric/monthly', async (c) => {
const customer = (c.req.query('customer') || 'lingniu') as CustomerKind;
const range = (c.req.query('range') || 'last15') as Range;
const force = c.req.query('force') === '1';
const data = await cached(`electric/monthly?customer=${customer}&range=${range}`, async () => {
@@ -590,7 +594,7 @@ app.get('/electric/monthly', async (c) => {
});
return months;
});
}, { force });
return c.json(data);
});