Skip to content

Commit e1c6756

Browse files
committed
feat(account): 完善账号代理筛选与诊断能力
- 后台账号列表支持按代理筛选,并同步批量编辑筛选条件 - 增加可选 pprof 诊断服务开关,便于运行期性能排查 - 补充账号筛选、批量更新、网关与邮箱验证相关测试适配 发布预检:暂存新增行敏感形态扫描通过;仓库既有敏感形态命中已由用户确认为可公开。
1 parent 815627d commit e1c6756

30 files changed

Lines changed: 355 additions & 49 deletions

‎backend/cmd/server/VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.15
1+
1.0.18

‎backend/cmd/server/main.go‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"flag"
1010
"log"
1111
"net/http"
12+
_ "net/http/pprof"
1213
"os"
1314
"os/signal"
15+
"strconv"
1416
"strings"
1517
"syscall"
1618
"time"
@@ -151,6 +153,8 @@ func runMainServer() {
151153
}
152154
defer app.Cleanup()
153155

156+
pprofServer := startPprofServer()
157+
154158
// 启动服务器
155159
go func() {
156160
if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
@@ -174,5 +178,47 @@ func runMainServer() {
174178
log.Fatalf("Server forced to shutdown: %v", err)
175179
}
176180

181+
if pprofServer != nil {
182+
if err := pprofServer.Shutdown(ctx); err != nil {
183+
log.Fatalf("pprof server forced to shutdown: %v", err)
184+
}
185+
}
186+
177187
log.Println("Server exited")
178188
}
189+
190+
func startPprofServer() *http.Server {
191+
enabledValue := strings.TrimSpace(os.Getenv("PPROF_ENABLED"))
192+
if enabledValue == "" {
193+
return nil
194+
}
195+
196+
enabled, err := strconv.ParseBool(enabledValue)
197+
if err != nil {
198+
log.Fatalf("Invalid PPROF_ENABLED value %q: %v", enabledValue, err)
199+
}
200+
if !enabled {
201+
return nil
202+
}
203+
204+
addr := strings.TrimSpace(os.Getenv("PPROF_ADDR"))
205+
if addr == "" {
206+
addr = "127.0.0.1:6060"
207+
}
208+
209+
server := &http.Server{
210+
Addr: addr,
211+
Handler: http.DefaultServeMux,
212+
ReadHeaderTimeout: 5 * time.Second,
213+
IdleTimeout: 30 * time.Second,
214+
}
215+
216+
go func() {
217+
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
218+
log.Fatalf("Failed to start pprof server on %s: %v", addr, err)
219+
}
220+
}()
221+
222+
log.Printf("pprof server started on %s", addr)
223+
return server
224+
}

‎backend/internal/handler/admin/account_data.go‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,12 +493,12 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
493493
return out, nil
494494
}
495495

496-
func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode, sortBy, sortOrder string) ([]service.Account, error) {
496+
func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string, groupID, proxyID int64, privacyMode, sortBy, sortOrder string) ([]service.Account, error) {
497497
page := 1
498498
pageSize := dataPageCap
499499
var out []service.Account
500500
for {
501-
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
501+
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, groupID, proxyID, privacyMode, sortBy, sortOrder)
502502
if err != nil {
503503
return nil, err
504504
}
@@ -551,7 +551,12 @@ func (h *AccountHandler) resolveExportAccounts(ctx context.Context, ids []int64,
551551
}
552552
}
553553

554-
return h.listAccountsFiltered(ctx, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
554+
proxyID, err := parseAccountProxyFilter(c)
555+
if err != nil {
556+
return nil, err
557+
}
558+
559+
return h.listAccountsFiltered(ctx, platform, accountType, status, search, groupID, proxyID, privacyMode, sortBy, sortOrder)
555560
}
556561

557562
func (h *AccountHandler) resolveExportProxies(ctx context.Context, accounts []service.Account) ([]service.Proxy, error) {

‎backend/internal/handler/admin/account_data_handler_test.go‎

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,88 @@ func setupAccountDataRouter() (*gin.Engine, *stubAdminService) {
7373
return router, adminSvc
7474
}
7575

76+
func setupAccountListRouter() (*gin.Engine, *stubAdminService) {
77+
gin.SetMode(gin.TestMode)
78+
router := gin.New()
79+
adminSvc := newStubAdminService()
80+
81+
h := NewAccountHandler(
82+
adminSvc,
83+
nil,
84+
nil,
85+
nil,
86+
nil,
87+
nil,
88+
nil,
89+
nil,
90+
nil,
91+
nil,
92+
nil,
93+
nil,
94+
nil,
95+
nil,
96+
)
97+
98+
router.GET("/api/v1/admin/accounts", h.List)
99+
return router, adminSvc
100+
}
101+
102+
func TestListAccountsPassesProxyFilter(t *testing.T) {
103+
router, adminSvc := setupAccountListRouter()
104+
105+
rec := httptest.NewRecorder()
106+
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?proxy_id=34", nil)
107+
router.ServeHTTP(rec, req)
108+
require.Equal(t, http.StatusOK, rec.Code)
109+
110+
require.Equal(t, 1, adminSvc.lastListAccounts.calls)
111+
require.Equal(t, int64(34), adminSvc.lastListAccounts.proxyID)
112+
}
113+
114+
func TestListAccountsPassesUnassignedProxyFilter(t *testing.T) {
115+
router, adminSvc := setupAccountListRouter()
116+
117+
rec := httptest.NewRecorder()
118+
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?proxy_id=-1", nil)
119+
router.ServeHTTP(rec, req)
120+
require.Equal(t, http.StatusOK, rec.Code)
121+
122+
require.Equal(t, 1, adminSvc.lastListAccounts.calls)
123+
require.Equal(t, service.AccountListProxyUnassigned, adminSvc.lastListAccounts.proxyID)
124+
}
125+
126+
func TestListAccountsRejectsInvalidProxyFilter(t *testing.T) {
127+
router, adminSvc := setupAccountListRouter()
128+
129+
rec := httptest.NewRecorder()
130+
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?proxy_id=abc", nil)
131+
router.ServeHTTP(rec, req)
132+
require.Equal(t, http.StatusBadRequest, rec.Code)
133+
require.Equal(t, 0, adminSvc.lastListAccounts.calls)
134+
}
135+
136+
func TestListAccountsRejectsUnsupportedNegativeProxyFilter(t *testing.T) {
137+
router, adminSvc := setupAccountListRouter()
138+
139+
rec := httptest.NewRecorder()
140+
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?proxy_id=-2", nil)
141+
router.ServeHTTP(rec, req)
142+
require.Equal(t, http.StatusBadRequest, rec.Code)
143+
require.Equal(t, 0, adminSvc.lastListAccounts.calls)
144+
}
145+
146+
func TestListAccountsPassesLegacyProxyFilterAlias(t *testing.T) {
147+
router, adminSvc := setupAccountListRouter()
148+
149+
rec := httptest.NewRecorder()
150+
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts?proxy=35", nil)
151+
router.ServeHTTP(rec, req)
152+
require.Equal(t, http.StatusOK, rec.Code)
153+
154+
require.Equal(t, 1, adminSvc.lastListAccounts.calls)
155+
require.Equal(t, int64(35), adminSvc.lastListAccounts.proxyID)
156+
}
157+
76158
func TestExportDataIncludesSecrets(t *testing.T) {
77159
router, adminSvc := setupAccountDataRouter()
78160

@@ -182,7 +264,7 @@ func TestExportDataPassesAccountFiltersAndSort(t *testing.T) {
182264
rec := httptest.NewRecorder()
183265
req := httptest.NewRequest(
184266
http.MethodGet,
185-
"/api/v1/admin/accounts/data?platform=openai&type=oauth&status=active&group=12&privacy_mode=blocked&search=keyword&sort_by=priority&sort_order=desc",
267+
"/api/v1/admin/accounts/data?platform=openai&type=oauth&status=active&group=12&proxy_id=34&privacy_mode=blocked&search=keyword&sort_by=priority&sort_order=desc",
186268
nil,
187269
)
188270
router.ServeHTTP(rec, req)
@@ -193,6 +275,7 @@ func TestExportDataPassesAccountFiltersAndSort(t *testing.T) {
193275
require.Equal(t, "oauth", adminSvc.lastListAccounts.accountType)
194276
require.Equal(t, "active", adminSvc.lastListAccounts.status)
195277
require.Equal(t, int64(12), adminSvc.lastListAccounts.groupID)
278+
require.Equal(t, int64(34), adminSvc.lastListAccounts.proxyID)
196279
require.Equal(t, "blocked", adminSvc.lastListAccounts.privacyMode)
197280
require.Equal(t, "keyword", adminSvc.lastListAccounts.search)
198281
require.Equal(t, "priority", adminSvc.lastListAccounts.sortBy)

‎backend/internal/handler/admin/account_handler.go‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ type BulkUpdateAccountFilters struct {
206206
Type string `json:"type"`
207207
Status string `json:"status"`
208208
Group string `json:"group"`
209+
ProxyID int64 `json:"proxy_id"`
209210
Search string `json:"search"`
210211
PrivacyMode string `json:"privacy_mode"`
211212
}
@@ -229,6 +230,22 @@ type AccountWithConcurrency struct {
229230

230231
const accountListGroupUngroupedQueryValue = "ungrouped"
231232

233+
func parseAccountProxyFilter(c *gin.Context) (int64, error) {
234+
raw := strings.TrimSpace(c.Query("proxy_id"))
235+
if raw == "" {
236+
raw = strings.TrimSpace(c.Query("proxy"))
237+
}
238+
if raw == "" {
239+
return 0, nil
240+
}
241+
242+
proxyID, err := strconv.ParseInt(raw, 10, 64)
243+
if err != nil || proxyID == 0 || proxyID < service.AccountListProxyUnassigned {
244+
return 0, infraerrors.BadRequest("INVALID_PROXY_FILTER", "invalid proxy filter")
245+
}
246+
return proxyID, nil
247+
}
248+
232249
const (
233250
adminOwnedPublicShareValidationQueueSize = 1024
234251
adminOwnedPublicShareValidationWorkers = 2
@@ -438,7 +455,13 @@ func (h *AccountHandler) List(c *gin.Context) {
438455
}
439456
}
440457

441-
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
458+
proxyID, err := parseAccountProxyFilter(c)
459+
if err != nil {
460+
response.ErrorFrom(c, err)
461+
return
462+
}
463+
464+
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, proxyID, privacyMode, sortBy, sortOrder)
442465
if err != nil {
443466
response.ErrorFrom(c, err)
444467
return
@@ -1802,6 +1825,7 @@ func toServiceBulkUpdateAccountFilters(filters *BulkUpdateAccountFilters) *servi
18021825
Type: filters.Type,
18031826
Status: filters.Status,
18041827
Group: filters.Group,
1828+
ProxyID: filters.ProxyID,
18051829
Search: filters.Search,
18061830
PrivacyMode: filters.PrivacyMode,
18071831
}
@@ -2416,7 +2440,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
24162440
accounts := make([]*service.Account, 0)
24172441

24182442
if len(req.AccountIDs) == 0 {
2419-
allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, "", "name", "asc")
2443+
allAccounts, _, err := h.adminService.ListAccounts(ctx, 1, 10000, "gemini", "oauth", "", "", 0, 0, "", "name", "asc")
24202444
if err != nil {
24212445
response.ErrorFrom(c, err)
24222446
return

‎backend/internal/handler/admin/admin_service_stub_test.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type stubAdminService struct {
3939
status string
4040
search string
4141
groupID int64
42+
proxyID int64
4243
privacyMode string
4344
sortBy string
4445
sortOrder string
@@ -295,12 +296,13 @@ func (s *stubAdminService) BatchSetGroupRPMOverrides(_ context.Context, _ int64,
295296
return nil
296297
}
297298

298-
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]service.Account, int64, error) {
299+
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID, proxyID int64, privacyMode string, sortBy, sortOrder string) ([]service.Account, int64, error) {
299300
s.lastListAccounts.platform = platform
300301
s.lastListAccounts.accountType = accountType
301302
s.lastListAccounts.status = status
302303
s.lastListAccounts.search = search
303304
s.lastListAccounts.groupID = groupID
305+
s.lastListAccounts.proxyID = proxyID
304306
s.lastListAccounts.privacyMode = privacyMode
305307
s.lastListAccounts.sortBy = sortBy
306308
s.lastListAccounts.sortOrder = sortOrder

‎backend/internal/handler/user_account_handler_batch_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (s *userAccountBatchRepoStub) Delete(context.Context, int64) error {
8484
func (s *userAccountBatchRepoStub) List(context.Context, pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
8585
panic("unexpected List call")
8686
}
87-
func (s *userAccountBatchRepoStub) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64, string) ([]service.Account, *pagination.PaginationResult, error) {
87+
func (s *userAccountBatchRepoStub) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64, int64, string) ([]service.Account, *pagination.PaginationResult, error) {
8888
panic("unexpected ListWithFilters call")
8989
}
9090
func (s *userAccountBatchRepoStub) ListByGroup(context.Context, int64) ([]service.Account, error) {

‎backend/internal/repository/account_repo.go‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -491,18 +491,18 @@ func (r *accountRepository) Delete(ctx context.Context, id int64) error {
491491
}
492492

493493
func (r *accountRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
494-
return r.ListWithFilters(ctx, params, "", "", "", "", 0, "")
494+
return r.ListWithFilters(ctx, params, "", "", "", "", 0, 0, "")
495495
}
496496

497-
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
498-
return r.listWithFilters(ctx, params, nil, platform, accountType, status, search, groupID, privacyMode)
497+
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID, proxyID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
498+
return r.listWithFilters(ctx, params, nil, platform, accountType, status, search, groupID, proxyID, privacyMode)
499499
}
500500

501-
func (r *accountRepository) ListOwnedWithFilters(ctx context.Context, ownerUserID int64, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
501+
func (r *accountRepository) ListOwnedWithFilters(ctx context.Context, ownerUserID int64, params pagination.PaginationParams, platform, accountType, status, search string, groupID, proxyID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
502502
if ownerUserID <= 0 {
503503
return nil, nil, service.ErrUserNotFound
504504
}
505-
return r.listWithFilters(ctx, params, &ownerUserID, platform, accountType, status, search, groupID, privacyMode)
505+
return r.listWithFilters(ctx, params, &ownerUserID, platform, accountType, status, search, groupID, proxyID, privacyMode)
506506
}
507507

508508
func (r *accountRepository) ListQuotaPoolAccounts(ctx context.Context, ownerUserID int64) ([]service.Account, error) {
@@ -773,7 +773,7 @@ func setNullStringExtra(extra map[string]any, key string, value sql.NullString)
773773
extra[key] = value.String
774774
}
775775

776-
func (r *accountRepository) listWithFilters(ctx context.Context, params pagination.PaginationParams, ownerUserID *int64, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
776+
func (r *accountRepository) listWithFilters(ctx context.Context, params pagination.PaginationParams, ownerUserID *int64, platform, accountType, status, search string, groupID, proxyID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
777777
q := r.client.Account.Query()
778778

779779
if ownerUserID != nil {
@@ -854,6 +854,11 @@ func (r *accountRepository) listWithFilters(ctx context.Context, params paginati
854854
} else if groupID > 0 {
855855
q = q.Where(dbaccount.HasAccountGroupsWith(dbaccountgroup.GroupIDEQ(groupID)))
856856
}
857+
if proxyID == service.AccountListProxyUnassigned {
858+
q = q.Where(dbaccount.ProxyIDIsNil())
859+
} else if proxyID > 0 {
860+
q = q.Where(dbaccount.ProxyIDEQ(proxyID))
861+
}
857862
if privacyMode != "" {
858863
q = q.Where(dbpredicate.Account(func(s *entsql.Selector) {
859864
path := sqljson.Path("privacy_mode")

0 commit comments

Comments
 (0)