// Copyright 2026 tamakyi. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package route import ( stdctx "context" "net/http" "time" "github.com/sirupsen/logrus" "github.com/tamakyi/TamaBox/internal/context" "github.com/tamakyi/TamaBox/internal/db" ) // readyzTimeout 就绪探针等待数据库的最长时间。 // 刻意短:探针本身不该成为慢请求,超时就视为「未就绪」并由上层重试。 const readyzTimeout = 2 * time.Second // Healthz 存活探针:进程还能处理请求就返回 200。 // // 刻意不碰任何外部依赖。「进程还活着」和「能对外服务」是两个问题: // 前者挂了才该重启,后者挂了该摘流 —— 后者看 Readyz。 // 把 DB 检查塞进存活探针是常见反模式:数据库抖动会导致进程被反复重启, // 而重启对恢复数据库毫无帮助,只会让在途请求全部失败。 func Healthz(ctx context.Context) { w := ctx.ResponseWriter() w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) } // Readyz 就绪探针:数据库可用才返回 200,否则 503。 // // 为什么需要这个端点:改动前站内唯一的「探活」手段是 curl /,而首页只渲染模板、 // 不查数据库 —— DB 挂了首页照样 200(漏报),Redis 挂了首页又可能 500(误报)。 // 于是监控、CDN 源站健康检查、滚动重启的摘流判定都没有可靠依据。 // // 响应体只回组件状态,绝不回错误详情:这是公开端点,数据库报错里常带库名、 // 主机名甚至账号信息。错误详情只写日志。 func Readyz(ctx context.Context) { reqCtx, cancel := stdctx.WithTimeout(ctx.Request().Context(), readyzTimeout) defer cancel() w := ctx.ResponseWriter() w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Cache-Control", "no-store") if err := db.Ping(reqCtx); err != nil { logrus.WithContext(reqCtx).WithError(err).Warn("就绪探针:数据库不可用") w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte(`{"status":"unavailable","database":"down"}`)) return } w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"status":"ok","database":"up"}`)) }