From aa0a623a73799caee6700f1b620093123ae70d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Tue, 2 Jun 2026 16:02:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Raw=20API=20HT?= =?UTF-8?q?ML=20=E5=93=8D=E5=BA=94=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E4=B8=8E=E8=AF=8A=E6=96=AD=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当 GitLink API 返回 HTML 页面(如登录页)而非 JSON 数据时, 自动识别并返回结构化错误信息,包含可能原因和修复建议。 Co-Authored-By: Claude Opus 4.7 --- internal/client/client.go | 36 ++++++++++++++++++- internal/client/client_test.go | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/internal/client/client.go b/internal/client/client.go index 1d20aed..94f0cb1 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -107,10 +107,21 @@ func (c *Client) Do(method, path string, body interface{}, query url.Values) (*o } } + // Detect HTML responses (GitLink returns login pages when auth is missing) + if detectHTMLResponse(respData) { + msg := "服务器返回了 HTML 页面而非 JSON 数据" + suggestion := suggestHTMLFix() + return output.ErrorEnvelope(resp.StatusCode, msg, suggestion), + &APIError{ + StatusCode: resp.StatusCode, + Code: "HTML_RESPONSE", + Message: msg + "\n" + suggestion, + } + } + // Parse JSON var raw map[string]interface{} if err := json.Unmarshal(respData, &raw); err != nil { - // Not JSON, return as-is return output.SuccessEnvelope(string(respData), nil), nil } @@ -215,3 +226,26 @@ func suggestFix(code int) string { return "" } } + +func detectHTMLResponse(data []byte) bool { + trimmed := bytes.TrimSpace(data) + if len(trimmed) == 0 { + return false + } + prefixes := []string{"...`, true}, + {"html 小写开头", `...`, true}, + {"HTML 大写开头", `...`, true}, + {"doctype 小写开头", ``, true}, + {"空响应体", "", false}, + {"纯文本", `just some text`, false}, + {"空白后 HTML", ` `, true}, + {"JSON 数组", `[1,2,3]`, false}, + {"HTML 片段(无前缀)", `content`, false}, + {"XML 声明后跟 HTML", ``, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := detectHTMLResponse([]byte(tt.body)); got != tt.wantHTML { + t.Errorf("detectHTMLResponse(%q) = %v, want %v", tt.body, got, tt.wantHTML) + } + }) + } +} + +func TestClientDoHTMLResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(`Sign inPlease log in`)) + })) + defer server.Close() + + c := &Client{HTTP: server.Client(), BaseURL: server.URL} + env, err := c.Do("GET", "/api/test", nil, nil) + if err == nil { + t.Fatal("expected error for HTML response") + } + if env == nil { + t.Fatal("expected envelope for HTML response") + } + if env.OK { + t.Fatal("expected OK=false for HTML response") + } + apiErr, ok := err.(*APIError) + if !ok { + t.Fatalf("expected *APIError, got %T", err) + } + if apiErr.Code != "HTML_RESPONSE" { + t.Fatalf("Code = %v, want HTML_RESPONSE", apiErr.Code) + } +} + +func TestSuggestHTMLFix(t *testing.T) { + msg := suggestHTMLFix() + if msg == "" { + t.Fatal("suggestHTMLFix should return a non-empty message") + } + if !strings.Contains(msg, "gitlink-cli auth login") { + t.Fatal("suggestHTMLFix should mention auth login") + } +} From 9ea0dc71b90ee9cda1ccc0e9b95b3353c34896db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=A4=A9=E7=BF=94?= Date: Tue, 2 Jun 2026 16:03:40 +0800 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20=E6=9B=B4=E6=96=B0=20CI=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=94=AF=E6=8C=81=20Go=201.26.1=20=E5=92=8C=E5=BB=BA?= =?UTF-8?q?=E6=9C=A8=E6=B5=81=E6=B0=B4=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gitea Actions: 更新 Go 版本为 1.26.1 以匹配 go.mod - 建木流水线: 新增 .devops/ci.yml,push 到 jtx_branch 自动触发 Co-Authored-By: Claude Opus 4.7 --- .devops/ci.yml | 41 +++++++++++++++++++++++++++++++++++++++++ .gitea/workflows/ci.yml | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .devops/ci.yml diff --git a/.devops/ci.yml b/.devops/ci.yml new file mode 100644 index 0000000..7fdd6d7 --- /dev/null +++ b/.devops/ci.yml @@ -0,0 +1,41 @@ +version: 2 +name: gitlink_cli_ci +description: "gitlink-cli 代码提交时自动执行 CI 检查(构建、测试、格式化)" +trigger: + webhook: gitlink@1.0.0 + event: + - ref: push + ruleset-operator: AND +global: + concurrent: 1 +workflow: + - ref: start + name: 开始 + task: start + - ref: git_clone_0 + name: 拉取代码 + task: git_clone@1.2.9 + input: + remote_url: '"https://gitlink.org.cn/jiangtx/gitlink-cli.git"' + ref: '"refs/heads/jtx_branch"' + commit_id: '""' + depth: 1 + needs: + - start + - ref: ssh_cmd_0 + name: CI 检查 + task: ssh_cmd@1.1.1 + input: + ssh_pass: ((gitlink_cli_ci.ssh_pass)) + ssh_ip: '"121.41.212.97"' + ssh_port: '"22"' + ssh_user: '"root"' + ssh_cmd: >- + "cd /root && rm -rf gitlink-cli && git clone --depth=1 -b jtx_branch https://gitlink.org.cn/jiangtx/gitlink-cli.git && cd gitlink-cli && export PATH=$PATH:/usr/local/go/bin && export GOPROXY=https://goproxy.cn,direct && go version && go build ./... && go vet ./... && go test -race ./... && output=$(gofmt -s -l .) && if [ -n \"$output\" ]; then echo '格式化检查失败:' && echo \"$output\" && exit 1; fi && echo '所有 CI 检查通过'" + needs: + - git_clone_0 + - ref: end + name: 结束 + task: end + needs: + - ssh_cmd_0 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 94fb619..a835c17 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.26.1' - name: Build run: go build ./...