Scale SSE replay window by visible cities
This commit is contained in:
+1
-1
@@ -192,7 +192,7 @@ Ops:
|
|||||||
- 支付相关路由:`no-store`
|
- 支付相关路由:`no-store`
|
||||||
- 当 detail 缓存只返回单模型或单日 forecast 时,前端会自动强刷完整 detail,并在补齐前显示同步提示 / 占位卡
|
- 当 detail 缓存只返回单模型或单日 forecast 时,前端会自动强刷完整 detail,并在补齐前显示同步提示 / 占位卡
|
||||||
- 今日日内分析打开时如果正在切换城市、日期或 detail 深度,弹窗会阻断旧内容点击并显示刷新锁
|
- 今日日内分析打开时如果正在切换城市、日期或 detail 深度,弹窗会阻断旧内容点击并显示刷新锁
|
||||||
- 终端图表订阅 `/api/events?cities=...&since_revision=...&replay_limit=500`,接收 `city_observation_patch.v1`;无 patch 超过 2 分钟时,可见图表才触发 60 秒兜底刷新
|
- 终端图表订阅 `/api/events?cities=...&since_revision=...&replay_limit=按可见城市数动态限制`,接收 `city_observation_patch.v1`;无 patch 超过 2 分钟时,可见图表才触发 60 秒兜底刷新
|
||||||
- 前端只消费 HTTP snapshot + SSE patch,不直接感知 Redis;Redis Stream / SQLite event log 都由后端统一封装
|
- 前端只消费 HTTP snapshot + SSE patch,不直接感知 Redis;Redis Stream / SQLite event log 都由后端统一封装
|
||||||
|
|
||||||
## Vercel 节流建议
|
## Vercel 节流建议
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ export function runTests() {
|
|||||||
assert(hook.includes("subscribedCities"), "frontend patch hook must track the visible city subscription set");
|
assert(hook.includes("subscribedCities"), "frontend patch hook must track the visible city subscription set");
|
||||||
assert(hook.includes("since_revision"), "frontend patch hook must reconnect with since_revision");
|
assert(hook.includes("since_revision"), "frontend patch hook must reconnect with since_revision");
|
||||||
assert(hook.includes("resync_required"), "frontend patch hook must react to server resync_required events");
|
assert(hook.includes("resync_required"), "frontend patch hook must react to server resync_required events");
|
||||||
|
assert(
|
||||||
|
hook.includes("SSE_REPLAY_EVENTS_PER_CITY") &&
|
||||||
|
hook.includes("resolveSseReplayLimit") &&
|
||||||
|
hook.includes('params.set("replay_limit", String(resolveSseReplayLimit(cities.length)))') &&
|
||||||
|
!hook.includes('params.set("replay_limit", "500")'),
|
||||||
|
"frontend patch hook should size SSE replay_limit by visible city count instead of always asking for 500 events",
|
||||||
|
);
|
||||||
assert(hook.includes("lastRevision"), "frontend patch hook must track the global last processed revision");
|
assert(hook.includes("lastRevision"), "frontend patch hook must track the global last processed revision");
|
||||||
assert(hook.includes("Map<"), "frontend patch hook must keep latest patches in a Map");
|
assert(hook.includes("Map<"), "frontend patch hook must keep latest patches in a Map");
|
||||||
assert(hook.includes("useLatestPatch"), "frontend patch hook must export useLatestPatch(city)");
|
assert(hook.includes("useLatestPatch"), "frontend patch hook must export useLatestPatch(city)");
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { resolveBackendApiUrl } from "@/lib/backend-api";
|
|||||||
|
|
||||||
const V1_EVENT_TYPE = "city_observation_patch.v1";
|
const V1_EVENT_TYPE = "city_observation_patch.v1";
|
||||||
const SSE_SUBSCRIPTION_RECONNECT_DELAY_MS = 150;
|
const SSE_SUBSCRIPTION_RECONNECT_DELAY_MS = 150;
|
||||||
|
const SSE_REPLAY_BASE_LIMIT = 60;
|
||||||
|
const SSE_REPLAY_EVENTS_PER_CITY = 24;
|
||||||
|
const SSE_REPLAY_MAX_LIMIT = 240;
|
||||||
|
|
||||||
export type CityPatch = {
|
export type CityPatch = {
|
||||||
type?: string;
|
type?: string;
|
||||||
@@ -91,12 +94,17 @@ function buildSseUrl(baseUrl: string) {
|
|||||||
if (lastRevision > 0) {
|
if (lastRevision > 0) {
|
||||||
params.set("since_revision", String(lastRevision));
|
params.set("since_revision", String(lastRevision));
|
||||||
}
|
}
|
||||||
params.set("replay_limit", "500");
|
params.set("replay_limit", String(resolveSseReplayLimit(cities.length)));
|
||||||
|
|
||||||
const query = params.toString();
|
const query = params.toString();
|
||||||
return query ? `${baseUrl}?${query}` : baseUrl;
|
return query ? `${baseUrl}?${query}` : baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveSseReplayLimit(cityCount: number) {
|
||||||
|
const requested = Math.max(1, cityCount) * SSE_REPLAY_EVENTS_PER_CITY;
|
||||||
|
return Math.max(SSE_REPLAY_BASE_LIMIT, Math.min(SSE_REPLAY_MAX_LIMIT, requested));
|
||||||
|
}
|
||||||
|
|
||||||
function currentConnectionKey() {
|
function currentConnectionKey() {
|
||||||
return `${useFallbackUrl ? "fallback" : "direct"}:${subscribedCityList().join("|")}:${lastRevision}`;
|
return `${useFallbackUrl ? "fallback" : "direct"}:${subscribedCityList().join("|")}:${lastRevision}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user