From 637bfa82101bd887789334630d26ca1582a71100 Mon Sep 17 00:00:00 2001 From: zenfun Date: Sat, 27 Dec 2025 13:24:13 +0800 Subject: [PATCH] feat(api): add public status endpoints with version injection Replace health_handler with status_handler providing public /status and /about endpoints. Add build-time version injection via ldflags in Makefile, and support --version/-v CLI flag. - Add /status endpoint returning runtime status, uptime, and version - Add /about endpoint with system metadata (name, description, repo) - Configure VERSION variable with git describe fallback - Update swagger docs and api.md for new public endpoints - Remove deprecated /api/status/test endpoint --- Makefile | 12 ++-- cmd/server/main.go | 13 +++- docs/api.md | 10 ++- docs/docs.go | 121 +++++++++++++++++++++------------ docs/swagger.json | 121 +++++++++++++++++++++------------ docs/swagger.yaml | 81 ++++++++++++++-------- internal/api/health_handler.go | 37 ---------- internal/api/status_handler.go | 81 ++++++++++++++++++++++ 8 files changed, 317 insertions(+), 159 deletions(-) delete mode 100644 internal/api/health_handler.go create mode 100644 internal/api/status_handler.go diff --git a/Makefile b/Makefile index 3439c0e..4926a38 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ .PHONY: all build swagger test clean dev +# Version injection - can be overridden via make build VERSION=v1.0.0 +VERSION ?= $(shell git describe --tags --always 2>/dev/null || echo "v0.1.0-dev") +LDFLAGS := -X github.com/ez-api/ez-api/internal/api.Version=$(VERSION) + # Default target all: swagger build @@ -10,12 +14,12 @@ swagger: # Build the binary build: swagger - @echo "Building ez-api..." - go build -o ez-api ./cmd/server + @echo "Building ez-api $(VERSION)..." + go build -ldflags "$(LDFLAGS)" -o ez-api ./cmd/server # Build without swagger regeneration (for quick iteration) build-fast: - go build -o ez-api ./cmd/server + go build -ldflags "$(LDFLAGS)" -o ez-api ./cmd/server # Run tests test: @@ -28,7 +32,7 @@ clean: # Run in development mode dev: swagger - go run ./cmd/server + go run -ldflags "$(LDFLAGS)" ./cmd/server # Format code fmt: diff --git a/cmd/server/main.go b/cmd/server/main.go index f87e79b..a704aef 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -77,6 +77,12 @@ func isOriginAllowed(allowed []string, origin string) bool { } func main() { + // Handle --version flag before any initialization + if len(os.Args) > 1 && (os.Args[1] == "--version" || os.Args[1] == "-v") { + fmt.Printf("ez-api %s\n", api.Version) + os.Exit(0) + } + logger, _ := logging.New(logging.Options{Service: "ez-api"}) if len(os.Args) > 1 && os.Args[1] == "import" { code := runImport(logger, os.Args[2:]) @@ -196,7 +202,7 @@ func main() { masterService := service.NewMasterService(db) statsService := service.NewStatsService(rdb) healthService := service.NewHealthCheckService(db, rdb) - healthHandler := api.NewHealthHandler(healthService) + statusHandler := api.NewStatusHandler(healthService) handler := api.NewHandler(db, logDB, syncService, logWriter, rdb, logPartitioner) adminHandler := api.NewAdminHandler(db, logDB, masterService, syncService, statsService, logPartitioner) @@ -265,7 +271,10 @@ func main() { } c.JSON(httpStatus, status) }) - r.GET("/api/status/test", healthHandler.TestDeps) + + // Public Status Endpoints + r.GET("/status", statusHandler.Status) + r.GET("/about", statusHandler.About) // Swagger Documentation r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/docs/api.md b/docs/api.md index 3ce0eb7..723825e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -128,8 +128,14 @@ graph TD ## 4. API 模块概览 -### 4.0 公开接口 (Auth API) - 无需中间件 -- **身份识别**:`GET /auth/whoami` - 根据 Token 返回身份类型和详细信息。 +### 4.0 公开接口 - 无需鉴权 +| 端点 | 说明 | 响应示例 | +|------|------|----------| +| `GET /health` | 健康检查(含依赖状态) | `{"status": "ok", "database": "up", "redis": "up", "uptime": "1h30m"}` | +| `GET /status` | 公开状态摘要 | `{"status": "ok", "uptime": "1h30m", "version": "v0.1.0"}` | +| `GET /about` | 系统信息 | `{"name": "EZ-API Gateway", "version": "v0.1.0", ...}` | +| `GET /auth/whoami` | 身份识别 | 根据 Token 返回身份类型和详细信息 | +| `GET /swagger/*` | API 文档 | Swagger UI | ### 4.1 管理端 (Admin API) - 需 Admin Token - **租户管理**:创建 Master、签发子 Key、实时 QPS 监控、冻结/解冻。 diff --git a/docs/docs.go b/docs/docs.go index 8f4fc4e..313fbdb 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -24,6 +24,26 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/about": { + "get": { + "description": "Returns system metadata for display on an about page", + "produces": [ + "application/json" + ], + "tags": [ + "Public" + ], + "summary": "Get system information", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_api.AboutResponse" + } + } + } + } + }, "/admin/api-keys": { "get": { "security": [ @@ -2844,32 +2864,6 @@ const docTemplate = `{ } } }, - "/api/status/test": { - "get": { - "description": "Checks Redis/PostgreSQL connections and reports status", - "produces": [ - "application/json" - ], - "tags": [ - "system" - ], - "summary": "Test dependency connectivity", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus" - } - }, - "503": { - "description": "Service Unavailable", - "schema": { - "$ref": "#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus" - } - } - } - } - }, "/auth/whoami": { "get": { "security": [ @@ -2990,6 +2984,26 @@ const docTemplate = `{ } } }, + "/status": { + "get": { + "description": "Returns public runtime status information without sensitive data", + "produces": [ + "application/json" + ], + "tags": [ + "Public" + ], + "summary": "Get system status", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_api.StatusResponse" + } + } + } + } + }, "/v1/logs": { "get": { "security": [ @@ -3925,23 +3939,6 @@ const docTemplate = `{ } } }, - "github_com_ez-api_ez-api_internal_service.HealthStatus": { - "type": "object", - "properties": { - "database": { - "type": "string" - }, - "redis": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uptime": { - "type": "string" - } - } - }, "github_com_ez-api_ez-api_internal_service.LogWebhookConfig": { "type": "object", "properties": { @@ -4049,6 +4046,27 @@ const docTemplate = `{ } } }, + "internal_api.AboutResponse": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "High-performance LLM API gateway" + }, + "name": { + "type": "string", + "example": "EZ-API Gateway" + }, + "repository": { + "type": "string", + "example": "https://github.com/ez-api/ez-api" + }, + "version": { + "type": "string", + "example": "0.1.0" + } + } + }, "internal_api.AccessResponse": { "type": "object", "properties": { @@ -4635,6 +4653,23 @@ const docTemplate = `{ } } }, + "internal_api.StatusResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + }, + "uptime": { + "type": "string", + "example": "72h30m15s" + }, + "version": { + "type": "string", + "example": "0.1.0" + } + } + }, "internal_api.TokenView": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 66f8bc5..b893e3e 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -18,6 +18,26 @@ "host": "localhost:8080", "basePath": "/", "paths": { + "/about": { + "get": { + "description": "Returns system metadata for display on an about page", + "produces": [ + "application/json" + ], + "tags": [ + "Public" + ], + "summary": "Get system information", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_api.AboutResponse" + } + } + } + } + }, "/admin/api-keys": { "get": { "security": [ @@ -2838,32 +2858,6 @@ } } }, - "/api/status/test": { - "get": { - "description": "Checks Redis/PostgreSQL connections and reports status", - "produces": [ - "application/json" - ], - "tags": [ - "system" - ], - "summary": "Test dependency connectivity", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus" - } - }, - "503": { - "description": "Service Unavailable", - "schema": { - "$ref": "#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus" - } - } - } - } - }, "/auth/whoami": { "get": { "security": [ @@ -2984,6 +2978,26 @@ } } }, + "/status": { + "get": { + "description": "Returns public runtime status information without sensitive data", + "produces": [ + "application/json" + ], + "tags": [ + "Public" + ], + "summary": "Get system status", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_api.StatusResponse" + } + } + } + } + }, "/v1/logs": { "get": { "security": [ @@ -3919,23 +3933,6 @@ } } }, - "github_com_ez-api_ez-api_internal_service.HealthStatus": { - "type": "object", - "properties": { - "database": { - "type": "string" - }, - "redis": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uptime": { - "type": "string" - } - } - }, "github_com_ez-api_ez-api_internal_service.LogWebhookConfig": { "type": "object", "properties": { @@ -4043,6 +4040,27 @@ } } }, + "internal_api.AboutResponse": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "High-performance LLM API gateway" + }, + "name": { + "type": "string", + "example": "EZ-API Gateway" + }, + "repository": { + "type": "string", + "example": "https://github.com/ez-api/ez-api" + }, + "version": { + "type": "string", + "example": "0.1.0" + } + } + }, "internal_api.AccessResponse": { "type": "object", "properties": { @@ -4629,6 +4647,23 @@ } } }, + "internal_api.StatusResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok" + }, + "uptime": { + "type": "string", + "example": "72h30m15s" + }, + "version": { + "type": "string", + "example": "0.1.0" + } + } + }, "internal_api.TokenView": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d21f473..48337b6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -247,17 +247,6 @@ definitions: updatedAt: type: string type: object - github_com_ez-api_ez-api_internal_service.HealthStatus: - properties: - database: - type: string - redis: - type: string - status: - type: string - uptime: - type: string - type: object github_com_ez-api_ez-api_internal_service.LogWebhookConfig: properties: cooldown_seconds: @@ -328,6 +317,21 @@ definitions: description: Valid is true if Time is not NULL type: boolean type: object + internal_api.AboutResponse: + properties: + description: + example: High-performance LLM API gateway + type: string + name: + example: EZ-API Gateway + type: string + repository: + example: https://github.com/ez-api/ez-api + type: string + version: + example: 0.1.0 + type: string + type: object internal_api.AccessResponse: properties: default_namespace: @@ -712,6 +716,18 @@ definitions: tokens: type: integer type: object + internal_api.StatusResponse: + properties: + status: + example: ok + type: string + uptime: + example: 72h30m15s + type: string + version: + example: 0.1.0 + type: string + type: object internal_api.TokenView: properties: allow_ips: @@ -943,6 +959,19 @@ info: title: EZ-API Control Plane version: 0.0.1 paths: + /about: + get: + description: Returns system metadata for display on an about page + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/internal_api.AboutResponse' + summary: Get system information + tags: + - Public /admin/api-keys: get: description: List API keys @@ -2744,23 +2773,6 @@ paths: summary: Force sync snapshot tags: - admin - /api/status/test: - get: - description: Checks Redis/PostgreSQL connections and reports status - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus' - "503": - description: Service Unavailable - schema: - $ref: '#/definitions/github_com_ez-api_ez-api_internal_service.HealthStatus' - summary: Test dependency connectivity - tags: - - system /auth/whoami: get: description: |- @@ -2845,6 +2857,19 @@ paths: summary: Ingest logs tags: - system + /status: + get: + description: Returns public runtime status information without sensitive data + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/internal_api.StatusResponse' + summary: Get system status + tags: + - Public /v1/logs: get: description: List request logs for the authenticated master diff --git a/internal/api/health_handler.go b/internal/api/health_handler.go deleted file mode 100644 index 93ece24..0000000 --- a/internal/api/health_handler.go +++ /dev/null @@ -1,37 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/ez-api/ez-api/internal/service" - "github.com/gin-gonic/gin" -) - -type HealthHandler struct { - svc *service.HealthCheckService -} - -func NewHealthHandler(svc *service.HealthCheckService) *HealthHandler { - return &HealthHandler{svc: svc} -} - -// TestDeps godoc -// @Summary Test dependency connectivity -// @Description Checks Redis/PostgreSQL connections and reports status -// @Tags system -// @Produce json -// @Success 200 {object} service.HealthStatus -// @Failure 503 {object} service.HealthStatus -// @Router /api/status/test [get] -func (h *HealthHandler) TestDeps(c *gin.Context) { - if h == nil || h.svc == nil { - c.JSON(http.StatusServiceUnavailable, gin.H{"status": "down"}) - return - } - status := h.svc.Check(c.Request.Context()) - httpStatus := http.StatusOK - if status.Status == "down" { - httpStatus = http.StatusServiceUnavailable - } - c.JSON(httpStatus, status) -} diff --git a/internal/api/status_handler.go b/internal/api/status_handler.go new file mode 100644 index 0000000..3bbc7ed --- /dev/null +++ b/internal/api/status_handler.go @@ -0,0 +1,81 @@ +package api + +import ( + "net/http" + "time" + + "github.com/ez-api/ez-api/internal/service" + "github.com/gin-gonic/gin" +) + +// Version is set at build time using ldflags +// e.g., go build -ldflags "-X github.com/ez-api/ez-api/internal/api.Version=v1.0.0" +var Version = "v0.1.0-dev" + +// startTime is used to calculate uptime +var startTime = time.Now() + +// StatusHandler handles public status endpoints +type StatusHandler struct { + healthService *service.HealthCheckService +} + +// NewStatusHandler creates a new StatusHandler +func NewStatusHandler(healthService *service.HealthCheckService) *StatusHandler { + return &StatusHandler{ + healthService: healthService, + } +} + +// StatusResponse represents the response for /status endpoint +type StatusResponse struct { + Status string `json:"status" example:"ok"` + Uptime string `json:"uptime" example:"72h30m15s"` + Version string `json:"version" example:"0.1.0"` +} + +// AboutResponse represents the response for /about endpoint +type AboutResponse struct { + Name string `json:"name" example:"EZ-API Gateway"` + Version string `json:"version" example:"0.1.0"` + Description string `json:"description" example:"High-performance LLM API gateway"` + Repository string `json:"repository" example:"https://github.com/ez-api/ez-api"` +} + +// Status godoc +// @Summary Get system status +// @Description Returns public runtime status information without sensitive data +// @Tags Public +// @Produce json +// @Success 200 {object} StatusResponse +// @Router /status [get] +func (h *StatusHandler) Status(c *gin.Context) { + // Check health status + health := h.healthService.Check(c.Request.Context()) + + resp := StatusResponse{ + Status: health.Status, + Uptime: time.Since(startTime).Round(time.Second).String(), + Version: Version, + } + + c.JSON(http.StatusOK, resp) +} + +// About godoc +// @Summary Get system information +// @Description Returns system metadata for display on an about page +// @Tags Public +// @Produce json +// @Success 200 {object} AboutResponse +// @Router /about [get] +func (h *StatusHandler) About(c *gin.Context) { + resp := AboutResponse{ + Name: "EZ-API Gateway", + Version: Version, + Description: "High-performance LLM API gateway", + Repository: "https://github.com/ez-api/ez-api", + } + + c.JSON(http.StatusOK, resp) +}