diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index 92ae09c4..e2818fa4 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -128,6 +128,19 @@ func (r *accountRepository) GetByID(ctx context.Context, id int64) (*service.Acc return &accounts[0], nil } +// ExistsByID 检查指定 ID 的账号是否存在。 +// 相比 GetByID,此方法性能更优,因为: +// - 使用 Exist() 方法生成 SELECT EXISTS 查询,只返回布尔值 +// - 不加载完整的账号实体及其关联数据(Groups、Proxy 等) +// - 适用于删除前的存在性检查等只需判断有无的场景 +func (r *accountRepository) ExistsByID(ctx context.Context, id int64) (bool, error) { + exists, err := r.client.Account.Query().Where(dbaccount.IDEQ(id)).Exist(ctx) + if err != nil { + return false, err + } + return exists, nil +} + func (r *accountRepository) GetByCRSAccountID(ctx context.Context, crsAccountID string) (*service.Account, error) { if crsAccountID == "" { return nil, nil diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go index 352cc6e1..5b998b1b 100644 --- a/backend/internal/repository/api_key_repo.go +++ b/backend/internal/repository/api_key_repo.go @@ -49,6 +49,25 @@ func (r *apiKeyRepository) GetByID(ctx context.Context, id int64) (*service.ApiK return apiKeyEntityToService(m), nil } +// GetOwnerID 根据 API Key ID 获取其所有者(用户)的 ID。 +// 相比 GetByID,此方法性能更优,因为: +// - 使用 Select() 只查询 user_id 字段,减少数据传输量 +// - 不加载完整的 ApiKey 实体及其关联数据(User、Group 等) +// - 适用于权限验证等只需用户 ID 的场景(如删除前的所有权检查) +func (r *apiKeyRepository) GetOwnerID(ctx context.Context, id int64) (int64, error) { + m, err := r.client.ApiKey.Query(). + Where(apikey.IDEQ(id)). + Select(apikey.FieldUserID). + Only(ctx) + if err != nil { + if dbent.IsNotFound(err) { + return 0, service.ErrApiKeyNotFound + } + return 0, err + } + return m.UserID, nil +} + func (r *apiKeyRepository) GetByKey(ctx context.Context, key string) (*service.ApiKey, error) { m, err := r.client.ApiKey.Query(). Where(apikey.KeyEQ(key)). diff --git a/backend/internal/service/account_service.go b/backend/internal/service/account_service.go index be70987c..f40db3d0 100644 --- a/backend/internal/service/account_service.go +++ b/backend/internal/service/account_service.go @@ -16,6 +16,8 @@ var ( type AccountRepository interface { Create(ctx context.Context, account *Account) error GetByID(ctx context.Context, id int64) (*Account, error) + // ExistsByID 检查账号是否存在,仅返回布尔值,用于删除前的轻量级存在性检查 + ExistsByID(ctx context.Context, id int64) (bool, error) // GetByCRSAccountID finds an account previously synced from CRS. // Returns (nil, nil) if not found. GetByCRSAccountID(ctx context.Context, crsAccountID string) (*Account, error) @@ -235,11 +237,17 @@ func (s *AccountService) Update(ctx context.Context, id int64, req UpdateAccount } // Delete 删除账号 +// 优化:使用 ExistsByID 替代 GetByID 进行存在性检查, +// 避免加载完整账号对象及其关联数据,提升删除操作的性能 func (s *AccountService) Delete(ctx context.Context, id int64) error { - // 检查账号是否存在 - _, err := s.accountRepo.GetByID(ctx, id) + // 使用轻量级的存在性检查,而非加载完整账号对象 + exists, err := s.accountRepo.ExistsByID(ctx, id) if err != nil { - return fmt.Errorf("get account: %w", err) + return fmt.Errorf("check account: %w", err) + } + // 明确返回账号不存在错误,便于调用方区分错误类型 + if !exists { + return ErrAccountNotFound } if err := s.accountRepo.Delete(ctx, id); err != nil { diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go index e6234382..facf997e 100644 --- a/backend/internal/service/api_key_service.go +++ b/backend/internal/service/api_key_service.go @@ -29,6 +29,8 @@ const ( type ApiKeyRepository interface { Create(ctx context.Context, key *ApiKey) error GetByID(ctx context.Context, id int64) (*ApiKey, error) + // GetOwnerID 仅获取 API Key 的所有者 ID,用于删除前的轻量级权限验证 + GetOwnerID(ctx context.Context, id int64) (int64, error) GetByKey(ctx context.Context, key string) (*ApiKey, error) Update(ctx context.Context, key *ApiKey) error Delete(ctx context.Context, id int64) error @@ -350,20 +352,23 @@ func (s *ApiKeyService) Update(ctx context.Context, id int64, userID int64, req } // Delete 删除API Key +// 优化:使用 GetOwnerID 替代 GetByID 进行权限验证, +// 避免加载完整 ApiKey 对象及其关联数据(User、Group),提升删除操作的性能 func (s *ApiKeyService) Delete(ctx context.Context, id int64, userID int64) error { - apiKey, err := s.apiKeyRepo.GetByID(ctx, id) + // 仅获取所有者 ID 用于权限验证,而非加载完整对象 + ownerID, err := s.apiKeyRepo.GetOwnerID(ctx, id) if err != nil { return fmt.Errorf("get api key: %w", err) } - // 验证所有权 - if apiKey.UserID != userID { + // 验证当前用户是否为该 API Key 的所有者 + if ownerID != userID { return ErrInsufficientPerms } - // 清除Redis缓存 + // 清除Redis缓存(使用 ownerID 而非 apiKey.UserID) if s.cache != nil { - _ = s.cache.DeleteCreateAttemptCount(ctx, apiKey.UserID) + _ = s.cache.DeleteCreateAttemptCount(ctx, ownerID) } if err := s.apiKeyRepo.Delete(ctx, id); err != nil { diff --git a/frontend/src/views/user/KeysView.vue b/frontend/src/views/user/KeysView.vue index d7df5e33..23caacd6 100644 --- a/frontend/src/views/user/KeysView.vue +++ b/frontend/src/views/user/KeysView.vue @@ -823,6 +823,11 @@ const handleSubmit = async () => { } } +/** + * 处理删除 API Key 的操作 + * 优化:错误处理改进,优先显示后端返回的具体错误消息(如权限不足等), + * 若后端未返回消息则显示默认的国际化文本 + */ const handleDelete = async () => { if (!selectedKey.value) return @@ -831,8 +836,10 @@ const handleDelete = async () => { appStore.showSuccess(t('keys.keyDeletedSuccess')) showDeleteDialog.value = false loadApiKeys() - } catch (error) { - appStore.showError(t('keys.failedToDelete')) + } catch (error: any) { + // 优先使用后端返回的错误消息,提供更具体的错误信息给用户 + const errorMsg = error?.message || t('keys.failedToDelete') + appStore.showError(errorMsg) } }