1
0

fix: 修复供应商管理弹窗交互问题并去掉 API Key 脱敏

- Dialog 设置 lazy={false} 修复首次打开编辑弹窗表单为空
- API Key 改为普通字段(前端去掉 password 类型,后端去掉掩码逻辑)
- 删除模型编辑弹窗中的统一模型 ID 字段
- 简化 ProviderService.Get 签名(去掉 maskKey 参数)
- 删除 domain 和 config 层的 MaskAPIKey() 方法
- 更新前后端测试(107 单元测试 + 16 E2E 全部通过)
- 同步 delta spec 到主 spec
This commit is contained in:
2026-04-22 13:13:25 +08:00
parent 81dcecb723
commit 5d58acf5a6
23 changed files with 68 additions and 128 deletions

View File

@@ -48,11 +48,3 @@ func (UsageStats) TableName() string {
return "usage_stats"
}
// MaskAPIKey 掩码 API Key仅显示最后 4 个字符)
func (p *Provider) MaskAPIKey() {
if len(p.APIKey) > 4 {
p.APIKey = "***" + p.APIKey[len(p.APIKey)-4:]
} else {
p.APIKey = "***"
}
}

View File

@@ -14,11 +14,3 @@ type Provider struct {
UpdatedAt time.Time `json:"updated_at"`
}
// MaskAPIKey 掩码 API Key仅显示最后 4 个字符)
func (p *Provider) MaskAPIKey() {
if len(p.APIKey) > 4 {
p.APIKey = "***" + p.APIKey[len(p.APIKey)-4:]
} else {
p.APIKey = "***"
}
}

View File

@@ -33,7 +33,7 @@ func TestProviderHandler_CreateProvider_Success(t *testing.T) {
var result domain.Provider
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
assert.Equal(t, "p1", result.ID)
assert.Contains(t, result.APIKey, "***")
assert.Equal(t, "sk-test", result.APIKey)
}
func TestProviderHandler_CreateProvider_WithProtocol(t *testing.T) {
@@ -57,7 +57,7 @@ func TestProviderHandler_CreateProvider_WithProtocol(t *testing.T) {
func TestProviderHandler_UpdateProvider(t *testing.T) {
h := NewProviderHandler(&mockProviderService{
provider: &domain.Provider{ID: "p1", Name: "Updated", APIKey: "***"},
provider: &domain.Provider{ID: "p1", Name: "Updated", APIKey: "sk-test"},
})
body, _ := json.Marshal(map[string]string{"name": "Updated"})

View File

@@ -65,7 +65,7 @@ func (m *mockProviderService) GetModelByProviderAndName(providerID, modelName st
}
func (m *mockProviderService) Create(provider *domain.Provider) error { return m.err }
func (m *mockProviderService) Get(id string, maskKey bool) (*domain.Provider, error) {
func (m *mockProviderService) Get(id string) (*domain.Provider, error) {
return m.provider, m.err
}
func (m *mockProviderService) List() ([]domain.Provider, error) { return m.providers, m.err }
@@ -148,7 +148,7 @@ func TestProviderHandler_ListProviders(t *testing.T) {
func TestProviderHandler_GetProvider(t *testing.T) {
h := NewProviderHandler(&mockProviderService{
provider: &domain.Provider{ID: "p1", Name: "P1", APIKey: "***"},
provider: &domain.Provider{ID: "p1", Name: "P1", APIKey: "sk-test"},
})
w := httptest.NewRecorder()

View File

@@ -66,7 +66,6 @@ func (h *ProviderHandler) CreateProvider(c *gin.Context) {
return
}
provider.MaskAPIKey()
c.JSON(http.StatusCreated, provider)
}
@@ -85,7 +84,7 @@ func (h *ProviderHandler) ListProviders(c *gin.Context) {
func (h *ProviderHandler) GetProvider(c *gin.Context) {
id := c.Param("id")
provider, err := h.providerService.Get(id, true)
provider, err := h.providerService.Get(id)
if err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{
@@ -131,7 +130,7 @@ func (h *ProviderHandler) UpdateProvider(c *gin.Context) {
return
}
provider, err := h.providerService.Get(id, true)
provider, err := h.providerService.Get(id)
if err != nil {
writeError(c, err)
return

View File

@@ -80,7 +80,7 @@ func (m *mockProxyProviderService) GetModelByProviderAndName(providerID, modelNa
}
func (m *mockProxyProviderService) Create(p *domain.Provider) error { return nil }
func (m *mockProxyProviderService) Get(id string, maskKey bool) (*domain.Provider, error) { return nil, nil }
func (m *mockProxyProviderService) Get(id string) (*domain.Provider, error) { return nil, nil }
func (m *mockProxyProviderService) List() ([]domain.Provider, error) { return m.providers, m.err }
func (m *mockProxyProviderService) Update(id string, updates map[string]interface{}) error { return nil }
func (m *mockProxyProviderService) Delete(id string) error { return nil }

View File

@@ -5,7 +5,7 @@ import "nex/backend/internal/domain"
// ProviderService 供应商服务接口
type ProviderService interface {
Create(provider *domain.Provider) error
Get(id string, maskKey bool) (*domain.Provider, error)
Get(id string) (*domain.Provider, error)
List() ([]domain.Provider, error)
Update(id string, updates map[string]interface{}) error
Delete(id string) error

View File

@@ -32,26 +32,12 @@ func (s *providerService) Create(provider *domain.Provider) error {
return err
}
func (s *providerService) Get(id string, maskKey bool) (*domain.Provider, error) {
provider, err := s.providerRepo.GetByID(id)
if err != nil {
return nil, err
}
if maskKey {
provider.MaskAPIKey()
}
return provider, nil
func (s *providerService) Get(id string) (*domain.Provider, error) {
return s.providerRepo.GetByID(id)
}
func (s *providerService) List() ([]domain.Provider, error) {
providers, err := s.providerRepo.List()
if err != nil {
return nil, err
}
for i := range providers {
providers[i].MaskAPIKey()
}
return providers, nil
return s.providerRepo.List()
}
func (s *providerService) Update(id string, updates map[string]interface{}) error {

View File

@@ -21,7 +21,7 @@ func TestProviderService_Update(t *testing.T) {
err := svc.Update("p1", map[string]interface{}{"name": "Updated"})
require.NoError(t, err)
result, err := svc.Get("p1", false)
result, err := svc.Get("p1")
require.NoError(t, err)
assert.Equal(t, "Updated", result.Name)
}

View File

@@ -268,7 +268,7 @@ func TestProviderService_Update_Success(t *testing.T) {
})
require.NoError(t, err)
updated, err := svc.Get("openai", false)
updated, err := svc.Get("openai")
require.NoError(t, err)
assert.Equal(t, "OpenAI Updated", updated.Name)
}

View File

@@ -533,8 +533,7 @@ func TestConversion_ProviderWithProtocol(t *testing.T) {
var created map[string]any
json.Unmarshal(w.Body.Bytes(), &created)
// API Key 被掩码
assert.Contains(t, created["api_key"], "***")
assert.Equal(t, "sk-test", created["api_key"])
// 获取时应包含 protocol
w = httptest.NewRecorder()

View File

@@ -103,7 +103,7 @@ func TestOpenAI_CompleteFlow(t *testing.T) {
var providers []domain.Provider
json.Unmarshal(w.Body.Bytes(), &providers)
assert.Len(t, providers, 1)
assert.Contains(t, providers[0].APIKey, "***") // 已掩码
assert.Equal(t, "sk-test-key", providers[0].APIKey)
// 4. 列出 Model
w = httptest.NewRecorder()