Skip to content

Commit 19988f8

Browse files
authored
feat: 优化密钥批量操作 (#211)
* feat: 异步批量删除秘钥 * fix: 优化删除sql,避免参数过长
1 parent 54f5616 commit 19988f8

File tree

14 files changed

+178
-16
lines changed

14 files changed

+178
-16
lines changed

internal/container/container.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func BuildContainer() (*dig.Container, error) {
5454
if err := container.Provide(services.NewKeyImportService); err != nil {
5555
return nil, err
5656
}
57+
if err := container.Provide(services.NewKeyDeleteService); err != nil {
58+
return nil, err
59+
}
5760
if err := container.Provide(services.NewLogService); err != nil {
5861
return nil, err
5962
}

internal/handler/handler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Server struct {
2525
TaskService *services.TaskService
2626
KeyService *services.KeyService
2727
KeyImportService *services.KeyImportService
28+
KeyDeleteService *services.KeyDeleteService
2829
LogService *services.LogService
2930
CommonHandler *CommonHandler
3031
}
@@ -40,6 +41,7 @@ type NewServerParams struct {
4041
TaskService *services.TaskService
4142
KeyService *services.KeyService
4243
KeyImportService *services.KeyImportService
44+
KeyDeleteService *services.KeyDeleteService
4345
LogService *services.LogService
4446
CommonHandler *CommonHandler
4547
}
@@ -55,6 +57,7 @@ func NewServer(params NewServerParams) *Server {
5557
TaskService: params.TaskService,
5658
KeyService: params.KeyService,
5759
KeyImportService: params.KeyImportService,
60+
KeyDeleteService: params.KeyDeleteService,
5861
LogService: params.LogService,
5962
CommonHandler: params.CommonHandler,
6063
}

internal/handler/key_handler.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,33 @@ func (s *Server) DeleteMultipleKeys(c *gin.Context) {
192192
response.Success(c, result)
193193
}
194194

195+
// DeleteMultipleKeysAsync handles deleting keys from a text block within a specific group using async task.
196+
func (s *Server) DeleteMultipleKeysAsync(c *gin.Context) {
197+
var req KeyTextRequest
198+
if err := c.ShouldBindJSON(&req); err != nil {
199+
response.Error(c, app_errors.NewAPIError(app_errors.ErrInvalidJSON, err.Error()))
200+
return
201+
}
202+
203+
group, ok := s.findGroupByID(c, req.GroupID)
204+
if !ok {
205+
return
206+
}
207+
208+
if err := validateKeysText(req.KeysText); err != nil {
209+
response.Error(c, app_errors.NewAPIError(app_errors.ErrValidation, err.Error()))
210+
return
211+
}
212+
213+
taskStatus, err := s.KeyDeleteService.StartDeleteTask(group, req.KeysText)
214+
if err != nil {
215+
response.Error(c, app_errors.NewAPIError(app_errors.ErrTaskInProgress, err.Error()))
216+
return
217+
}
218+
219+
response.Success(c, taskStatus)
220+
}
221+
195222
// RestoreMultipleKeys handles restoring keys from a text block within a specific group.
196223
func (s *Server) RestoreMultipleKeys(c *gin.Context) {
197224
var req KeyTextRequest

internal/keypool/provider.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,11 @@ func (p *KeyProvider) removeKeysByStatus(groupID uint, status ...string) (int64,
477477
return nil
478478
}
479479

480-
result := tx.Where("id IN ?", pluckIDs(keysToRemove)).Delete(&models.APIKey{})
480+
deleteQuery := tx.Where("group_id = ?", groupID)
481+
if len(status) > 0 {
482+
deleteQuery = deleteQuery.Where("status IN ?", status)
483+
}
484+
result := deleteQuery.Delete(&models.APIKey{})
481485
if result.Error != nil {
482486
return result.Error
483487
}

internal/router/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func registerProtectedAPIRoutes(api *gin.RouterGroup, serverHandler *handler.Ser
122122
keys.POST("/add-multiple", serverHandler.AddMultipleKeys)
123123
keys.POST("/add-async", serverHandler.AddMultipleKeysAsync)
124124
keys.POST("/delete-multiple", serverHandler.DeleteMultipleKeys)
125+
keys.POST("/delete-async", serverHandler.DeleteMultipleKeysAsync)
125126
keys.POST("/restore-multiple", serverHandler.RestoreMultipleKeys)
126127
keys.POST("/restore-all-invalid", serverHandler.RestoreAllInvalidKeys)
127128
keys.POST("/clear-all-invalid", serverHandler.ClearAllInvalidKeys)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package services
2+
3+
import (
4+
"fmt"
5+
"gpt-load/internal/models"
6+
"time"
7+
8+
"github.com/sirupsen/logrus"
9+
)
10+
11+
const (
12+
deleteChunkSize = 1000
13+
deleteTimeout = 30 * time.Minute
14+
)
15+
16+
// KeyDeleteResult holds the result of a delete task.
17+
type KeyDeleteResult struct {
18+
DeletedCount int `json:"deleted_count"`
19+
IgnoredCount int `json:"ignored_count"`
20+
}
21+
22+
// KeyDeleteService handles the asynchronous deletion of a large number of keys.
23+
type KeyDeleteService struct {
24+
TaskService *TaskService
25+
KeyService *KeyService
26+
}
27+
28+
// NewKeyDeleteService creates a new KeyDeleteService.
29+
func NewKeyDeleteService(taskService *TaskService, keyService *KeyService) *KeyDeleteService {
30+
return &KeyDeleteService{
31+
TaskService: taskService,
32+
KeyService: keyService,
33+
}
34+
}
35+
36+
// StartDeleteTask initiates a new asynchronous key deletion task.
37+
func (s *KeyDeleteService) StartDeleteTask(group *models.Group, keysText string) (*TaskStatus, error) {
38+
keys := s.KeyService.ParseKeysFromText(keysText)
39+
if len(keys) == 0 {
40+
return nil, fmt.Errorf("no valid keys found in the input text")
41+
}
42+
43+
initialStatus, err := s.TaskService.StartTask(TaskTypeKeyDelete, group.Name, len(keys), deleteTimeout)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
go s.runDelete(group, keys)
49+
50+
return initialStatus, nil
51+
}
52+
53+
func (s *KeyDeleteService) runDelete(group *models.Group, keys []string) {
54+
progressCallback := func(processed int) {
55+
if err := s.TaskService.UpdateProgress(processed); err != nil {
56+
logrus.Warnf("Failed to update task progress for group %d: %v", group.ID, err)
57+
}
58+
}
59+
60+
deletedCount, ignoredCount, err := s.processAndDeleteKeys(group.ID, keys, progressCallback)
61+
if err != nil {
62+
if endErr := s.TaskService.EndTask(nil, err); endErr != nil {
63+
logrus.Errorf("Failed to end task with error for group %d: %v (original error: %v)", group.ID, endErr, err)
64+
}
65+
return
66+
}
67+
68+
result := KeyDeleteResult{
69+
DeletedCount: deletedCount,
70+
IgnoredCount: ignoredCount,
71+
}
72+
73+
if endErr := s.TaskService.EndTask(result, nil); endErr != nil {
74+
logrus.Errorf("Failed to end task with success result for group %d: %v", group.ID, endErr)
75+
}
76+
}
77+
78+
// processAndDeleteKeys is the core function for deleting keys with progress tracking.
79+
func (s *KeyDeleteService) processAndDeleteKeys(
80+
groupID uint,
81+
keys []string,
82+
progressCallback func(processed int),
83+
) (deletedCount int, ignoredCount int, err error) {
84+
var totalDeletedCount int64
85+
86+
for i := 0; i < len(keys); i += deleteChunkSize {
87+
end := i + deleteChunkSize
88+
if end > len(keys) {
89+
end = len(keys)
90+
}
91+
chunk := keys[i:end]
92+
93+
deletedChunkCount, err := s.KeyService.KeyProvider.RemoveKeys(groupID, chunk)
94+
if err != nil {
95+
return int(totalDeletedCount), len(keys) - int(totalDeletedCount), err
96+
}
97+
98+
totalDeletedCount += deletedChunkCount
99+
100+
if progressCallback != nil {
101+
progressCallback(i + len(chunk))
102+
}
103+
}
104+
105+
return int(totalDeletedCount), len(keys) - int(totalDeletedCount), nil
106+
}

internal/services/key_import_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
const (
1212
importChunkSize = 1000
13-
importTimeout = 24 * time.Hour
13+
importTimeout = 30 * time.Minute
1414
)
1515

1616
// KeyImportResult holds the result of an import task.

internal/services/task_service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
const (
1717
TaskTypeKeyValidation = "KEY_VALIDATION"
1818
TaskTypeKeyImport = "KEY_IMPORT"
19+
TaskTypeKeyDelete = "KEY_DELETE"
1920
)
2021

2122
// TaskStatus represents the full lifecycle of a long-running task.

web/src/api/keys.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ export const keysApi = {
143143
return res.data;
144144
},
145145

146+
// 异步批量删除密钥
147+
async deleteKeysAsync(group_id: number, keys_text: string): Promise<TaskInfo> {
148+
const res = await http.post("/keys/delete-async", {
149+
group_id,
150+
keys_text,
151+
});
152+
return res.data;
153+
},
154+
146155
// 测试密钥
147156
restoreKeys(group_id: number, keys_text: string): Promise<null> {
148157
return http.post("/keys/restore-multiple", {

web/src/components/GlobalTaskProgressBar.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ async function pollOnce() {
5353
} else if (task.task_type === "KEY_IMPORT") {
5454
const result = task.result as import("@/types/models").KeyImportResult;
5555
msg = `密钥导入完成,成功添加 ${result.added_count} 个密钥,忽略了 ${result.ignored_count} 个。`;
56+
} else if (task.task_type === "KEY_DELETE") {
57+
const result = task.result as import("@/types/models").KeyDeleteResult;
58+
msg = `密钥删除完成,成功删除 ${result.deleted_count} 个密钥,忽略了 ${result.ignored_count} 个。`;
5659
}
5760
5861
message.info(msg, {
@@ -119,6 +122,8 @@ function getTaskTitle(): string {
119122
return `正在验证分组 [${taskInfo.value.group_name}] 的密钥`;
120123
case "KEY_IMPORT":
121124
return `正在向分组 [${taskInfo.value.group_name}] 导入密钥`;
125+
case "KEY_DELETE":
126+
return `正在删除分组 [${taskInfo.value.group_name}] 的密钥`;
122127
default:
123128
return "正在处理任务...";
124129
}

0 commit comments

Comments
 (0)