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
This commit is contained in:
zenfun
2025-12-27 13:24:13 +08:00
parent 3d39c591fd
commit 637bfa8210
8 changed files with 317 additions and 159 deletions

View File

@@ -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:

View File

@@ -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))

View File

@@ -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 监控、冻结/解冻。

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}