1
0

feat: 配置 golangci-lint 静态分析并修复存量违规

- 新增 backend/.golangci.yml 配置 12 个 linter(forbidigo、errorlint、errcheck、staticcheck、revive、gocritic、gosec、bodyclose、noctx、nilerr、goimports、gocyclo)
- 新增 lefthook.yml 配置 pre-commit hook 自动运行 lint
- 修复存量代码违规:errors.Is/As 替换、zap.Error 替换、import 排序、errcheck 修复
- 更新 README 补充编码规范说明
- 归档 backend-code-lint 变更
This commit is contained in:
2026-04-24 13:01:48 +08:00
parent 4c78ab6cc8
commit 4c6b49099d
96 changed files with 1290 additions and 1348 deletions

View File

@@ -11,20 +11,21 @@ import (
"testing"
"time"
"nex/backend/internal/conversion"
"nex/backend/internal/conversion/anthropic"
"nex/backend/internal/handler"
"nex/backend/internal/handler/middleware"
"nex/backend/internal/provider"
"nex/backend/internal/repository"
"nex/backend/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"gorm.io/gorm"
"nex/backend/internal/conversion"
"nex/backend/internal/conversion/anthropic"
openaiConv "nex/backend/internal/conversion/openai"
"nex/backend/internal/handler"
"nex/backend/internal/handler/middleware"
"nex/backend/internal/provider"
"nex/backend/internal/repository"
"nex/backend/internal/service"
)
func init() {
@@ -39,7 +40,8 @@ func setupConversionTest(t *testing.T) (*gin.Engine, *gorm.DB, *httptest.Server)
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 默认返回成功,由各测试 case 覆盖
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"error":"not mocked"}`))
_, err := w.Write([]byte(`{"error":"not mocked"}`))
require.NoError(t, err)
}))
db := setupTestDB(t)
@@ -124,7 +126,6 @@ func createProviderAndModel(t *testing.T, r *gin.Engine, providerID, protocol, m
require.Equal(t, 201, w.Code)
modelBody, _ := json.Marshal(map[string]string{
"provider_id": providerID,
"model_name": modelName,
})
@@ -143,9 +144,10 @@ func TestConversion_OpenAIToAnthropic_NonStream(t *testing.T) {
// 配置上游返回 Anthropic 格式响应
upstream.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 验证请求被转换为 Anthropic 格式
body, _ := io.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
var req map[string]any
json.Unmarshal(body, &req)
require.NoError(t, json.Unmarshal(body, &req))
assert.Equal(t, "/v1/messages", r.URL.Path)
assert.Contains(t, r.Header.Get("Content-Type"), "application/json")
@@ -166,7 +168,7 @@ func TestConversion_OpenAIToAnthropic_NonStream(t *testing.T) {
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
require.NoError(t, json.NewEncoder(w).Encode(resp))
})
createProviderAndModel(t, r, "anthropic_p", "anthropic", "claude-3-opus", upstream.URL)
@@ -189,13 +191,16 @@ func TestConversion_OpenAIToAnthropic_NonStream(t *testing.T) {
assert.Equal(t, 200, w.Code)
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(t, "chat.completion", resp["object"])
choices := resp["choices"].([]any)
choices, ok := resp["choices"].([]any)
require.True(t, ok)
require.Len(t, choices, 1)
choice := choices[0].(map[string]any)
msg := choice["message"].(map[string]any)
choice, ok := choices[0].(map[string]any)
require.True(t, ok)
msg, ok := choice["message"].(map[string]any)
require.True(t, ok)
assert.Contains(t, msg["content"], "Hello from Anthropic!")
}
@@ -203,9 +208,10 @@ func TestConversion_AnthropicToOpenAI_NonStream(t *testing.T) {
r, _, upstream := setupConversionTest(t)
upstream.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
var req map[string]any
json.Unmarshal(body, &req)
require.NoError(t, json.Unmarshal(body, &req))
assert.Equal(t, "/chat/completions", r.URL.Path)
assert.Contains(t, r.Header.Get("Authorization"), "Bearer test-key")
@@ -229,7 +235,7 @@ func TestConversion_AnthropicToOpenAI_NonStream(t *testing.T) {
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
require.NoError(t, json.NewEncoder(w).Encode(resp))
})
createProviderAndModel(t, r, "openai_p", "openai", "gpt-4", upstream.URL)
@@ -252,12 +258,14 @@ func TestConversion_AnthropicToOpenAI_NonStream(t *testing.T) {
assert.Equal(t, 200, w.Code)
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(t, "message", resp["type"])
content := resp["content"].([]any)
content, ok := resp["content"].([]any)
require.True(t, ok)
require.Len(t, content, 1)
block := content[0].(map[string]any)
block, ok2 := content[0].(map[string]any)
require.True(t, ok2)
assert.Contains(t, block["text"], "Hello from OpenAI!")
}
@@ -269,21 +277,23 @@ func TestConversion_OpenAIToOpenAI_Passthrough(t *testing.T) {
upstream.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/chat/completions", r.URL.Path)
body, _ := io.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
var req map[string]any
json.Unmarshal(body, &req)
require.NoError(t, json.Unmarshal(body, &req))
// Smart Passthrough: 请求体中的统一 ID 应被改写为上游模型名
assert.Equal(t, "gpt-4", req["model"])
w.Header().Set("Content-Type", "application/json")
// 上游返回上游模型名
w.Write([]byte(`{"id":"chatcmpl-pass","object":"chat.completion","model":"gpt-4","choices":[{"index":0,"message":{"role":"assistant","content":"passthrough"},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":1,"total_tokens":6}}`))
_, err = w.Write([]byte(`{"id":"chatcmpl-pass","object":"chat.completion","model":"gpt-4","choices":[{"index":0,"message":{"role":"assistant","content":"passthrough"},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":1,"total_tokens":6}}`))
require.NoError(t, err)
})
createProviderAndModel(t, r, "openai_p", "openai", "gpt-4", upstream.URL)
reqBody := map[string]any{
"model": "openai_p/gpt-4", // 客户端发送统一 ID
"model": "openai_p/gpt-4", // 客户端发送统一 ID
"messages": []map[string]any{{"role": "user", "content": "test"}},
}
body, _ := json.Marshal(reqBody)
@@ -304,21 +314,23 @@ func TestConversion_AnthropicToAnthropic_Passthrough(t *testing.T) {
upstream.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/v1/messages", r.URL.Path)
body, _ := io.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
var req map[string]any
json.Unmarshal(body, &req)
require.NoError(t, json.Unmarshal(body, &req))
// Smart Passthrough: 请求体中的统一 ID 应被改写为上游模型名
assert.Equal(t, "claude-3-opus", req["model"])
// 上游返回上游模型名
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"id":"msg-pass","type":"message","role":"assistant","model":"claude-3-opus","content":[{"type":"text","text":"passthrough"}],"stop_reason":"end_turn","usage":{"input_tokens":5,"output_tokens":1}}`))
_, err = w.Write([]byte(`{"id":"msg-pass","type":"message","role":"assistant","model":"claude-3-opus","content":[{"type":"text","text":"passthrough"}],"stop_reason":"end_turn","usage":{"input_tokens":5,"output_tokens":1}}`))
require.NoError(t, err)
})
createProviderAndModel(t, r, "anthropic_p", "anthropic", "claude-3-opus", upstream.URL)
reqBody := map[string]any{
"model": "anthropic_p/claude-3-opus", // 客户端发送统一 ID
"model": "anthropic_p/claude-3-opus", // 客户端发送统一 ID
"max_tokens": 1024,
"messages": []map[string]any{{"role": "user", "content": "test"}},
}
@@ -352,7 +364,8 @@ func TestConversion_OpenAIToAnthropic_Stream(t *testing.T) {
"event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
}
for _, e := range events {
w.Write([]byte(e))
_, err := w.Write([]byte(e))
require.NoError(t, err)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
@@ -393,7 +406,8 @@ func TestConversion_AnthropicToOpenAI_Stream(t *testing.T) {
"data: [DONE]\n\n",
}
for _, e := range events {
w.Write([]byte(e))
_, err := w.Write([]byte(e))
require.NoError(t, err)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
@@ -447,11 +461,13 @@ func TestConversion_Models_CrossProtocol(t *testing.T) {
require.NoError(t, err)
var anthropicResp map[string]any
json.Unmarshal(anthropicBody, &anthropicResp)
data := anthropicResp["data"].([]any)
require.NoError(t, json.Unmarshal(anthropicBody, &anthropicResp))
data, okd := anthropicResp["data"].([]any)
require.True(t, okd)
assert.Len(t, data, 2)
first := data[0].(map[string]any)
first, okf := data[0].(map[string]any)
require.True(t, okf)
assert.Equal(t, "gpt-4", first["id"])
assert.Equal(t, "model", first["type"])
@@ -466,11 +482,12 @@ func TestConversion_Models_CrossProtocol(t *testing.T) {
require.NoError(t, err)
var openaiResp map[string]any
json.Unmarshal(openaiBody, &err)
json.Unmarshal(openaiBody, &openaiResp)
oaiData := openaiResp["data"].([]any)
require.NoError(t, json.Unmarshal(openaiBody, &openaiResp))
oaiData, oki := openaiResp["data"].([]any)
require.True(t, oki)
assert.Len(t, oaiData, 1)
firstOai := oaiData[0].(map[string]any)
firstOai, okf2 := oaiData[0].(map[string]any)
require.True(t, okf2)
assert.Equal(t, "claude-3-opus", firstOai["id"])
}
@@ -537,7 +554,7 @@ func TestConversion_ProviderWithProtocol(t *testing.T) {
require.Equal(t, 201, w.Code)
var created map[string]any
json.Unmarshal(w.Body.Bytes(), &created)
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &created))
assert.Equal(t, "sk-test", created["api_key"])
// 获取时应包含 protocol
@@ -547,7 +564,7 @@ func TestConversion_ProviderWithProtocol(t *testing.T) {
assert.Equal(t, 200, w.Code)
var fetched map[string]any
json.Unmarshal(w.Body.Bytes(), &fetched)
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &fetched))
assert.Equal(t, "anthropic", fetched["protocol"])
}
@@ -570,11 +587,13 @@ func TestConversion_ProviderDefaultProtocol(t *testing.T) {
require.Equal(t, 201, w.Code)
var created map[string]any
json.Unmarshal(w.Body.Bytes(), &created)
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &created))
assert.Equal(t, "openai", created["protocol"])
}
// Suppress unused imports
var _ = fmt.Sprintf
var _ = strings.Contains
var _ = time.Second
var (
_ = fmt.Sprintf
_ = strings.Contains
_ = time.Second
)