From 28f377f2bd53fe60e0d7827bcd5113b16c2755ce Mon Sep 17 00:00:00 2001 From: phamnazage-jpg Date: Tue, 2 Jun 2026 07:00:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20M-04=20=E6=B7=BB=E5=8A=A0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 /version 端点返回版本信息 - 版本变量支持构建时 ldflags 注入 - 返回 version、commit、build_time、go_version --- internal/app/http_api.go | 27 +++++++++++++ internal/app/version_test.go | 73 ++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 internal/app/version_test.go diff --git a/internal/app/http_api.go b/internal/app/http_api.go index ba6670b4..75ca7f5e 100644 --- a/internal/app/http_api.go +++ b/internal/app/http_api.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" "strconv" "strings" "time" @@ -25,6 +26,13 @@ import ( "sub2api-cn-relay-manager/internal/access" ) +// 版本信息变量,在构建时通过 -ldflags 注入 +var ( + version = "dev" + commit = "unknown" + buildTime = "unknown" +) + type ActionSet struct { CreateBatchImportRun func(context.Context, CreateBatchImportRunRequest) (BatchImportRunCreateResponse, error) ListBatchImportRuns func(context.Context, ListBatchImportRunsRequest) (ListBatchImportRunsResponse, error) @@ -327,6 +335,7 @@ func NewAPIHandler(adminToken string, actions ActionSet) http.Handler { func NewAPIHandlerWithAuth(adminAuth AdminAuthConfig, actions ActionSet) http.Handler { mux := http.NewServeMux() mux.HandleFunc("GET /healthz", healthz) + mux.HandleFunc("GET /version", handleVersion) mux.HandleFunc("GET /api/admin/session", func(w http.ResponseWriter, r *http.Request) { handleAdminSessionState(w, r, adminAuth) }) @@ -557,6 +566,24 @@ func healthz(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) } +// VersionInfo 包含版本信息响应 +type VersionInfo struct { + Version string `json:"version"` + Commit string `json:"commit"` + BuildTime string `json:"build_time"` + GoVersion string `json:"go_version"` +} + +func handleVersion(w http.ResponseWriter, r *http.Request) { + info := VersionInfo{ + Version: version, + Commit: commit, + BuildTime: buildTime, + GoVersion: runtime.Version(), + } + writeJSON(w, http.StatusOK, info) +} + func handleCreateProviderDraft(w http.ResponseWriter, r *http.Request, fn func(context.Context, CreateProviderDraftRequest) (ProviderDraftInfo, error)) { if fn == nil { writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "create-provider-draft action is not configured"}) diff --git a/internal/app/version_test.go b/internal/app/version_test.go new file mode 100644 index 00000000..bded372d --- /dev/null +++ b/internal/app/version_test.go @@ -0,0 +1,73 @@ +package app + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "runtime" + "testing" +) + +func TestHandleVersion(t *testing.T) { + req := httptest.NewRequest("GET", "/version", nil) + rr := httptest.NewRecorder() + + handleVersion(rr, req) + + if rr.Code != http.StatusOK { + t.Errorf("Expected status 200, got %d", rr.Code) + } + + contentType := rr.Header().Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Expected Content-Type application/json, got %s", contentType) + } + + var info VersionInfo + if err := json.Unmarshal(rr.Body.Bytes(), &info); err != nil { + t.Fatalf("Failed to decode response: %v", err) + } + + // 验证版本字段 + if info.Version == "" { + t.Error("Version should not be empty") + } + if info.Version != version { + t.Errorf("Version = %q, want %q", info.Version, version) + } + + // 验证 Go 版本 + if info.GoVersion != runtime.Version() { + t.Errorf("GoVersion = %q, want %q", info.GoVersion, runtime.Version()) + } + + // Commit 和 BuildTime 可能为默认值 + if info.Commit == "" { + t.Error("Commit should not be empty (may be 'unknown' during development)") + } + if info.BuildTime == "" { + t.Error("BuildTime should not be empty (may be 'unknown' during development)") + } +} + +func TestVersionEndpoint_Integration(t *testing.T) { + handler := NewAPIHandler("", ActionSet{}) + + req := httptest.NewRequest("GET", "/version", nil) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Errorf("Expected status 200, got %d", rr.Code) + } + + var info VersionInfo + if err := json.Unmarshal(rr.Body.Bytes(), &info); err != nil { + t.Fatalf("Failed to decode response: %v", err) + } + + if info.Version != version { + t.Errorf("Version = %q, want %q", info.Version, version) + } +}