=== 1. 为什么去掉「显示条数」选择器 === topk 的 N 不影响查询成本(扫描的 chunk 集合完全相同),实测: topk(10) 17833ms / 11 条 topk(30) 18019ms / 34 条 topk(50) 7394ms / 54 条 topk(100) 17553ms / 74 条 波动来自集群负载,与 N 无关 —— 它只决定最后返回多少序列。 所以"选 30"既不更快也不更慢,本质是个翻页开关; 而选它还要重新等一次十几秒的查询,纯亏。 改为:一次 topk(50),前端 10 条/页 → 5 页只需 1 次查询。 附带收益:占比的分母从"前 10 合计"变成"前 50 合计",更接近真实占比。 副标题明确写清"一次取前 50 个应用(占比按这 50 个合计计算)"。 === 2. 零点结算昨天(daily volume)=== 原来只有 30 分钟 ticker 回补,且启动时间不对齐整点 → 昨天最早的一段可能已经超出 Loki 24h 保留期被删, 结算结果系统性偏低,图上却标"完整一天",属静默错数。 新增 nextMidnightFinalize():每天 00:01 触发 FinalizeYesterdayVolume() - 昨天定稿 partial=false - 补一次条数(index/stats,一天一次可接受) - 顺带刷新今天 partial=true 的柱子 ticker 仍保留,负责当天数据的 30 分钟刷新。 === 3. 顺带 === - 去掉每日量"其中 N 天平台没有留存记录"的提示文案(按用户要求) - 修 QuickQueryTemplates.tsx 的未使用变量导致 tsc -b 失败 - 工作区里上一轮中断的"排行体积列"改动一并纳入(app-ranking 返回 bytes, TopAppsTable 增加体积列,快照记录 per-app 字节数) Pod = Hub latest = a16978d2f88b
89 lines
2.9 KiB
Go
89 lines
2.9 KiB
Go
package routers
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// 回归:接口的 **HTTP 方法与路径** 必须和前端调用一致。
|
||
//
|
||
// 线上事故:一次模糊匹配的 patch 把
|
||
//
|
||
// logs.POST("/histogram", ...) → logs.GET("/histogram", ...)
|
||
//
|
||
// 前端 logsApi.histogram 用的是 POST(body 里带 sourceId/query/start/end),
|
||
// 于是所有直方图请求 404:
|
||
// - 日志查询页:直方图不显示
|
||
// - 日志分析页:直接报 "API endpoint not found",整页不可用
|
||
//
|
||
// 这类"方法写错"编译期完全查不出来,只能靠路由表断言。
|
||
func TestCriticalRoutes(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
engine := SetupRouter()
|
||
|
||
// 期望的 method + path(注意 gin 会把 :param 保留为 :id)
|
||
expect := []struct{ method, path string }{
|
||
// 日志查询 / 分析页的数据来源:必须是 POST(前端传 body)
|
||
{"POST", "/api/v1/logs/histogram"},
|
||
{"POST", "/api/v1/logs/stats"},
|
||
{"POST", "/api/v1/logs/metrics"},
|
||
{"POST", "/api/v1/logs/query"},
|
||
{"POST", "/api/v1/logs/context"},
|
||
// 查询辅助接口:GET + query string
|
||
{"GET", "/api/v1/logs/sources"},
|
||
{"GET", "/api/v1/logs/labels"},
|
||
{"GET", "/api/v1/logs/label/:name/values"},
|
||
// 批量 label 值(下拉联动一次请求)—— 路由写错就是前端全部下拉空
|
||
{"GET", "/api/v1/logs/label-values"},
|
||
{"GET", "/api/v1/logs/my-scope"},
|
||
{"GET", "/api/v1/logs/daily-volume"},
|
||
{"GET", "/api/v1/logs/health"},
|
||
// 告警
|
||
{"GET", "/api/v1/alerts/rules"},
|
||
{"POST", "/api/v1/alerts/rules"},
|
||
{"GET", "/api/v1/alerts/channels"},
|
||
{"POST", "/api/v1/alerts/check"},
|
||
{"GET", "/api/v1/alerts/deliveries"},
|
||
// 审计 / 自检
|
||
{"GET", "/api/v1/audit-logs"},
|
||
{"GET", "/api/v1/system/self-check"},
|
||
// 设置类:保留期必须同时有读和写(写要管理员)
|
||
{"GET", "/api/v1/settings/retention"},
|
||
{"PUT", "/api/v1/settings/retention"},
|
||
// 备份恢复接口已在上游被移除(commit f87fb4b),这里不再断言
|
||
}
|
||
|
||
registered := map[string]bool{}
|
||
for _, route := range engine.Routes() {
|
||
registered[route.Method+" "+route.Path] = true
|
||
}
|
||
|
||
for _, item := range expect {
|
||
key := item.method + " " + item.path
|
||
if !registered[key] {
|
||
t.Errorf("缺少路由: %s(前端按这个方法和路径调用,缺了就是 404)", key)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 回归:前端用 POST 的接口不能只有 GET 版本。
|
||
// 单独断言一次,避免以后又有人"顺手"把 POST 改成 GET。
|
||
func TestHistogramAcceptsPost(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
engine := SetupRouter()
|
||
|
||
methods := map[string]bool{}
|
||
for _, route := range engine.Routes() {
|
||
if route.Path == "/api/v1/logs/histogram" {
|
||
methods[route.Method] = true
|
||
}
|
||
}
|
||
if !methods["POST"] {
|
||
t.Fatal("POST /api/v1/logs/histogram 未注册:前端 logsApi.histogram 用 POST,会 404")
|
||
}
|
||
if !methods["GET"] {
|
||
t.Fatal("GET /api/v1/logs/histogram 未注册:curl 调试入口丢了")
|
||
}
|
||
}
|