From b33e50696d113652de13cb5e470d5020a125a4a7 Mon Sep 17 00:00:00 2001 From: JustMe Date: Mon, 19 Oct 2026 11:29:34 +0800 Subject: [PATCH] fix(api): never cache anchor=latest windows as immutable [CI] --- backend/src/MoShiro.Backend.Api/Common/QueryHelpers.cs | 5 ++++- backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/MoShiro.Backend.Api/Common/QueryHelpers.cs b/backend/src/MoShiro.Backend.Api/Common/QueryHelpers.cs index f37b141..b4dd594 100644 --- a/backend/src/MoShiro.Backend.Api/Common/QueryHelpers.cs +++ b/backend/src/MoShiro.Backend.Api/Common/QueryHelpers.cs @@ -32,7 +32,10 @@ internal static class QueryHelpers /// public static IResult? ApplyCaching(HttpContext http, QueryRange range, long nowMs, string material) { - bool closed = !range.IsRelative && range.RequestedToMs < nowMs - (2 * Steps.Minute); + // anchor=latest 的相对时间段会被换成以“最后一个样本”为终点的绝对区间:线路停过一阵再恢复时,同一个地址的内容就会变, + // 不能当成“已经结束的区间”永久缓存(浏览器会把某一刻的旧数据一直留着,页面上就是线路条不更新)。 + bool anchored = string.Equals(http.Request.Query["anchor"], "latest", StringComparison.OrdinalIgnoreCase); + bool closed = !anchored && !range.IsRelative && range.RequestedToMs < nowMs - (2 * Steps.Minute); if (!closed) { http.Response.Headers.CacheControl = "no-store"; diff --git a/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs b/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs index b4453d4..7726962 100644 --- a/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/IngestAndQueryTests.cs @@ -96,6 +96,10 @@ public sealed class IngestAndQueryTests : IClassFixture Assert.Equal(600, anchored["summary"]!["samples"]!["udp_echo"]!.GetValue()); Assert.False(anchored["live"]!.GetValue()); + // 这个窗口是以“最后一个样本”为终点的绝对区间,但线路恢复之后同一个地址的内容就变了:不能被浏览器永久缓存 + using HttpResponseMessage raw = await _backend.Admin.GetAsync($"/api/public/lines/{_backend.Line(id)}/series?range=5m&step=10s&metrics=loss.total&anchor=latest"); + Assert.Equal("no-store", raw.Headers.CacheControl!.ToString()); + // anchor=now is the default behaviour; anything else is rejected JsonNode explicitNow = await SeriesAsync(id, "range=5m&step=10s&metrics=loss.total&anchor=now"); Assert.Null(explicitNow["summary"]!["samples"]!["udp_echo"]);