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:
@@ -1,11 +1,15 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
"errors"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
)
|
||||
|
||||
type modelService struct {
|
||||
@@ -108,7 +112,11 @@ func (s *modelService) Delete(id string) error {
|
||||
func (s *modelService) checkDuplicateModelName(providerID, modelName, excludeID string) error {
|
||||
existing, err := s.modelRepo.FindByProviderAndModelName(providerID, modelName)
|
||||
if err != nil {
|
||||
return nil // 未找到,不重复
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil // 未找到,不重复
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
if excludeID != "" && existing.ID == excludeID {
|
||||
return nil // 排除自身
|
||||
|
||||
@@ -3,10 +3,10 @@ package service
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"nex/backend/pkg/modelid"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
"nex/backend/pkg/modelid"
|
||||
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -34,7 +35,9 @@ func NewRoutingCache(
|
||||
|
||||
func (c *RoutingCache) GetProvider(id string) (*domain.Provider, error) {
|
||||
if v, ok := c.providers.Load(id); ok {
|
||||
return v.(*domain.Provider), nil
|
||||
if provider, ok := v.(*domain.Provider); ok {
|
||||
return provider, nil
|
||||
}
|
||||
}
|
||||
|
||||
provider, err := c.providerRepo.GetByID(id)
|
||||
@@ -43,7 +46,9 @@ func (c *RoutingCache) GetProvider(id string) (*domain.Provider, error) {
|
||||
}
|
||||
|
||||
if v, ok := c.providers.Load(id); ok {
|
||||
return v.(*domain.Provider), nil
|
||||
if provider, ok := v.(*domain.Provider); ok {
|
||||
return provider, nil
|
||||
}
|
||||
}
|
||||
|
||||
c.providers.Store(id, provider)
|
||||
@@ -54,7 +59,9 @@ func (c *RoutingCache) GetModel(providerID, modelName string) (*domain.Model, er
|
||||
key := providerID + "/" + modelName
|
||||
|
||||
if v, ok := c.models.Load(key); ok {
|
||||
return v.(*domain.Model), nil
|
||||
if model, ok := v.(*domain.Model); ok {
|
||||
return model, nil
|
||||
}
|
||||
}
|
||||
|
||||
model, err := c.modelRepo.FindByProviderAndModelName(providerID, modelName)
|
||||
@@ -63,7 +70,9 @@ func (c *RoutingCache) GetModel(providerID, modelName string) (*domain.Model, er
|
||||
}
|
||||
|
||||
if v, ok := c.models.Load(key); ok {
|
||||
return v.(*domain.Model), nil
|
||||
if model, ok := v.(*domain.Model); ok {
|
||||
return model, nil
|
||||
}
|
||||
}
|
||||
|
||||
c.models.Store(key, model)
|
||||
@@ -97,7 +106,12 @@ func (c *RoutingCache) invalidateModelsByProvider(providerID string) {
|
||||
prefix := providerID + "/"
|
||||
count := 0
|
||||
c.models.Range(func(key, value interface{}) bool {
|
||||
if strings.HasPrefix(key.(string), prefix) {
|
||||
keyStr, ok := key.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(keyStr, prefix) {
|
||||
c.models.Delete(key)
|
||||
count++
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
)
|
||||
|
||||
type mockModelRepo struct {
|
||||
@@ -189,7 +189,8 @@ func TestRoutingCache_InvalidateProvider_CascadingModels(t *testing.T) {
|
||||
|
||||
var openaiCount, anthropicCount int
|
||||
cache.models.Range(func(key, value interface{}) bool {
|
||||
if key.(string) == "anthropic/claude" {
|
||||
keyStr, ok := key.(string)
|
||||
if ok && keyStr == "anthropic/claude" {
|
||||
anthropicCount++
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
)
|
||||
|
||||
type routingService struct {
|
||||
|
||||
@@ -3,12 +3,12 @@ package service
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
)
|
||||
|
||||
func TestProviderService_Update(t *testing.T) {
|
||||
@@ -133,7 +133,9 @@ func TestStatsService_Aggregate_Default(t *testing.T) {
|
||||
|
||||
totalCount := 0
|
||||
for _, r := range result {
|
||||
totalCount += r["request_count"].(int)
|
||||
count, ok := r["request_count"].(int)
|
||||
require.True(t, ok)
|
||||
totalCount += count
|
||||
}
|
||||
assert.Equal(t, 15, totalCount)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -13,8 +16,6 @@ import (
|
||||
|
||||
testHelpers "nex/backend/tests"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
"nex/backend/internal/repository"
|
||||
appErrors "nex/backend/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -134,7 +135,7 @@ func TestModelService_Create_ProviderNotFound(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
providerRepo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewModelService(modelRepo, providerRepo, cache)
|
||||
|
||||
model := &domain.Model{ProviderID: "nonexistent", ModelName: "gpt-4"}
|
||||
@@ -148,7 +149,7 @@ func TestProviderService_Create_InvalidID(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
repo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewProviderService(repo, modelRepo, cache)
|
||||
|
||||
provider := &domain.Provider{ID: "open-ai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}
|
||||
@@ -160,7 +161,7 @@ func TestProviderService_Create_ValidID(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
repo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewProviderService(repo, modelRepo, cache)
|
||||
|
||||
provider := &domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}
|
||||
@@ -176,7 +177,7 @@ func TestModelService_Update_DuplicateModelName(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
providerRepo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewModelService(modelRepo, providerRepo, cache)
|
||||
|
||||
require.NoError(t, providerRepo.Create(&domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}))
|
||||
@@ -202,7 +203,7 @@ func TestModelService_Update_ModelNotFound(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
providerRepo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewModelService(modelRepo, providerRepo, cache)
|
||||
|
||||
err := svc.Update("nonexistent-id", map[string]interface{}{
|
||||
@@ -215,7 +216,7 @@ func TestModelService_Update_Success(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
providerRepo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewModelService(modelRepo, providerRepo, cache)
|
||||
|
||||
require.NoError(t, providerRepo.Create(&domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}))
|
||||
@@ -241,7 +242,7 @@ func TestProviderService_Update_ImmutableID(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
repo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewProviderService(repo, modelRepo, cache)
|
||||
|
||||
provider := &domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}
|
||||
@@ -259,7 +260,7 @@ func TestProviderService_Update_Success(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
repo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewProviderService(repo, modelRepo, cache)
|
||||
|
||||
provider := &domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}
|
||||
@@ -318,7 +319,8 @@ func TestStatsService_Aggregate_ByModel(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
statsRepo := repository.NewStatsRepository(db)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop()); svc := NewStatsService(statsRepo, buffer)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop())
|
||||
svc := NewStatsService(statsRepo, buffer)
|
||||
|
||||
result := svc.Aggregate(tt.stats, "model")
|
||||
|
||||
@@ -379,7 +381,8 @@ func TestStatsService_Aggregate_ByDate(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
statsRepo := repository.NewStatsRepository(db)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop()); svc := NewStatsService(statsRepo, buffer)
|
||||
buffer := NewStatsBuffer(statsRepo, zap.NewNop())
|
||||
svc := NewStatsService(statsRepo, buffer)
|
||||
|
||||
result := svc.Aggregate(tt.stats, "date")
|
||||
|
||||
@@ -448,7 +451,7 @@ func TestProviderService_List_APIKeyNotMasked(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
repo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewProviderService(repo, modelRepo, cache)
|
||||
|
||||
provider1 := &domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "sk-1234567890", BaseURL: "https://api.openai.com"}
|
||||
@@ -474,7 +477,7 @@ func TestModelService_ConcurrentCreate(t *testing.T) {
|
||||
db := setupServiceTestDB(t)
|
||||
providerRepo := repository.NewProviderRepository(db)
|
||||
modelRepo := repository.NewModelRepository(db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
cache := setupRoutingCache(t, db)
|
||||
svc := NewModelService(modelRepo, providerRepo, cache)
|
||||
|
||||
require.NoError(t, providerRepo.Create(&domain.Provider{ID: "openai", Name: "OpenAI", APIKey: "key", BaseURL: "https://api.openai.com"}))
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"nex/backend/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/repository"
|
||||
pkglogger "nex/backend/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -67,13 +68,21 @@ func (b *StatsBuffer) Increment(providerID, modelName string) {
|
||||
|
||||
var counter *int64
|
||||
if v, ok := b.counters.Load(key); ok {
|
||||
counter = v.(*int64)
|
||||
if existing, ok := v.(*int64); ok {
|
||||
counter = existing
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
val := int64(0)
|
||||
counter = &val
|
||||
actual, loaded := b.counters.LoadOrStore(key, counter)
|
||||
if loaded {
|
||||
counter = actual.(*int64)
|
||||
existing, ok := actual.(*int64)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
counter = existing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,13 +126,20 @@ func (b *StatsBuffer) flush() {
|
||||
|
||||
var entries []statEntry
|
||||
b.counters.Range(func(key, value interface{}) bool {
|
||||
keyStr := key.(string)
|
||||
keyStr, ok := key.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, "/")
|
||||
if len(parts) != 3 {
|
||||
return true
|
||||
}
|
||||
|
||||
counter := value.(*int64)
|
||||
counter, ok := value.(*int64)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
count := atomic.SwapInt64(counter, 0)
|
||||
|
||||
if count > 0 {
|
||||
@@ -143,8 +159,17 @@ func (b *StatsBuffer) flush() {
|
||||
|
||||
success := 0
|
||||
for _, entry := range entries {
|
||||
date, _ := time.Parse("2006-01-02", entry.date)
|
||||
err := b.statsRepo.BatchUpdate(entry.providerID, entry.modelName, date, int(entry.count))
|
||||
date, err := time.Parse("2006-01-02", entry.date)
|
||||
if err != nil {
|
||||
b.logger.Error("解析统计日期失败",
|
||||
zap.String("provider_id", entry.providerID),
|
||||
zap.String("model_name", entry.modelName),
|
||||
zap.String("date", entry.date),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.statsRepo.BatchUpdate(entry.providerID, entry.modelName, date, int(entry.count))
|
||||
if err != nil {
|
||||
b.logger.Error("批量更新统计失败",
|
||||
zap.String("provider_id", entry.providerID),
|
||||
@@ -154,8 +179,10 @@ func (b *StatsBuffer) flush() {
|
||||
|
||||
key := entry.providerID + "/" + entry.modelName + "/" + entry.date
|
||||
if v, ok := b.counters.Load(key); ok {
|
||||
counter := v.(*int64)
|
||||
atomic.AddInt64(counter, entry.count)
|
||||
counter, ok := v.(*int64)
|
||||
if ok {
|
||||
atomic.AddInt64(counter, entry.count)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success++
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"nex/backend/internal/domain"
|
||||
)
|
||||
|
||||
type mockStatsRepo struct {
|
||||
@@ -58,8 +58,10 @@ func TestStatsBuffer_Increment(t *testing.T) {
|
||||
|
||||
var count int64
|
||||
buffer.counters.Range(func(key, value interface{}) bool {
|
||||
counter := value.(*int64)
|
||||
count += atomic.LoadInt64(counter)
|
||||
counter, ok := value.(*int64)
|
||||
if ok {
|
||||
count += atomic.LoadInt64(counter)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, int64(3), count)
|
||||
@@ -82,8 +84,10 @@ func TestStatsBuffer_ConcurrentIncrement(t *testing.T) {
|
||||
|
||||
var count int64
|
||||
buffer.counters.Range(func(key, value interface{}) bool {
|
||||
counter := value.(*int64)
|
||||
count = atomic.LoadInt64(counter)
|
||||
counter, ok := value.(*int64)
|
||||
if ok {
|
||||
count = atomic.LoadInt64(counter)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, int64(100), count)
|
||||
@@ -161,8 +165,10 @@ func TestStatsBuffer_SwapInt64(t *testing.T) {
|
||||
|
||||
var beforeCount int64
|
||||
buffer.counters.Range(func(key, value interface{}) bool {
|
||||
counter := value.(*int64)
|
||||
beforeCount = atomic.LoadInt64(counter)
|
||||
counter, ok := value.(*int64)
|
||||
if ok {
|
||||
beforeCount = atomic.LoadInt64(counter)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, int64(2), beforeCount)
|
||||
@@ -171,8 +177,10 @@ func TestStatsBuffer_SwapInt64(t *testing.T) {
|
||||
|
||||
var afterCount int64
|
||||
buffer.counters.Range(func(key, value interface{}) bool {
|
||||
counter := value.(*int64)
|
||||
afterCount = atomic.LoadInt64(counter)
|
||||
counter, ok := value.(*int64)
|
||||
if ok {
|
||||
afterCount = atomic.LoadInt64(counter)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, int64(0), afterCount)
|
||||
@@ -190,8 +198,10 @@ func TestStatsBuffer_FailRetry(t *testing.T) {
|
||||
|
||||
var count int64
|
||||
buffer.counters.Range(func(key, value interface{}) bool {
|
||||
counter := value.(*int64)
|
||||
count = atomic.LoadInt64(counter)
|
||||
counter, ok := value.(*int64)
|
||||
if ok {
|
||||
count = atomic.LoadInt64(counter)
|
||||
}
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, int64(2), count)
|
||||
|
||||
Reference in New Issue
Block a user