()
| 191 | } |
| 192 | |
| 193 | func (h *Handler) buildStatPayload() (*statPayload, error) { |
| 194 | allNodes, err := h.nodeStore.List() |
| 195 | if err != nil { |
| 196 | return nil, err |
| 197 | } |
| 198 | // 监控数据只包含启用的节点 |
| 199 | nodeList := make([]nodes.Node, 0, len(allNodes)) |
| 200 | for _, n := range allNodes { |
| 201 | if !n.Disabled { |
| 202 | nodeList = append(nodeList, n) |
| 203 | } |
| 204 | } |
| 205 | checkMap, _ := h.nodeStore.ListAllNodeCheckResults() |
| 206 | speedMap, _ := h.nodeStore.ListAllNodeSpeedTests() |
| 207 | uptimeBarsMap, _ := h.nodeStore.ListNodeUptimeBars(3) |
| 208 | tracerouteMap, _ := h.nodeStore.ListLatestTracerouteSnapshots() |
| 209 | |
| 210 | // 拉取过去 1 小时的延迟采样,按 node_id 分组 |
| 211 | nodeIDs := make([]string, 0, len(nodeList)) |
| 212 | for _, n := range nodeList { |
| 213 | nodeIDs = append(nodeIDs, n.ID) |
| 214 | } |
| 215 | latencyMap := make(map[string][]statLatencySample) |
| 216 | latencyNow := time.Now().UTC() |
| 217 | if samples, err := h.nodeStore.QueryLatencySamples(nodeIDs, latencyNow.Add(-1*time.Hour), latencyNow); err == nil { |
| 218 | for _, s := range samples { |
| 219 | var rtt *int |
| 220 | if s.RttMs != nil { |
| 221 | v := *s.RttMs |
| 222 | rtt = &v |
| 223 | } |
| 224 | latencyMap[s.NodeID] = append(latencyMap[s.NodeID], statLatencySample{ |
| 225 | ISP: s.ISP, |
| 226 | RttMs: rtt, |
| 227 | SampledAt: s.SampledAt.Format(time.RFC3339), |
| 228 | }) |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | toChecks := func(crs []nodes.CheckResult) []statCheck { |
| 233 | out := make([]statCheck, 0, len(crs)) |
| 234 | for _, cr := range crs { |
| 235 | jc := statCheck{Service: cr.Service, Unlocked: cr.Unlocked, Region: cr.Region, Note: cr.Note} |
| 236 | if !cr.CheckedAt.IsZero() { |
| 237 | jc.CheckedAt = cr.CheckedAt.Format(time.RFC3339) |
| 238 | } |
| 239 | out = append(out, jc) |
| 240 | } |
| 241 | return out |
| 242 | } |
| 243 | |
| 244 | // 预构建节点 IP → GeoIP 信息映射(DB 未就绪时静默跳过) |
| 245 | geoMap := make(map[string]*statNodeGeo, len(nodeList)) |
| 246 | if h.geoDB != nil && h.geoDB.Ready() { |
| 247 | for _, n := range nodeList { |
| 248 | host := strings.TrimSpace(n.IPOverride) |
| 249 | if host == "" { |
| 250 | // 从 base_url 提取 hostname |
no test coverage detected