From 61d2aa47ea5cd95defc34270cc59c2ad9b4ebe79 Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Sun, 8 Nov 2026 18:56:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=B8=80=E9=94=AE=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E4=B8=8E=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=94=99=E8=AF=AF/=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=B6=85=E6=97=B6/=E7=BB=91=E5=AE=9A=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/cmd/server/main.go | 4 +- backend/internal/handler/rock.go | 8 +- backend/internal/router/router.go | 2 +- .../internal/service/rock_mismatch_scan.go | 113 --------------- backend/internal/service/rock_overdue_test.go | 134 +++++++++++++++++- backend/internal/service/rock_stale_scan.go | 128 +++++++++++++++++ frontend/src/api/index.ts | 8 +- frontend/src/pages/PlatformRock.tsx | 24 ++-- .../pages/platformRock/RockTokenFilterBar.tsx | 35 +++-- 9 files changed, 303 insertions(+), 153 deletions(-) delete mode 100644 backend/internal/service/rock_mismatch_scan.go create mode 100644 backend/internal/service/rock_stale_scan.go diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 84c3b72..49d548d 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -175,8 +175,8 @@ func main() { log.Printf("[Rock] Token 检查失败: %v", err) } rockSvc.CheckStockAndAutoPurchase(context.Background()) - // 绑定错误自动刷新:内部会检查 auto_refresh_mismatch_enabled 开关,关闭时直接跳过。 - go rockSvc.RefreshMismatchTokens(context.Background()) + // 异常 Token 自动刷新:内部会检查 auto_refresh_mismatch_enabled 开关,关闭时直接跳过。 + go rockSvc.RefreshStaleTokens(context.Background()) } run() ticker := time.NewTicker(5 * time.Minute) diff --git a/backend/internal/handler/rock.go b/backend/internal/handler/rock.go index c778c0d..415bb69 100644 --- a/backend/internal/handler/rock.go +++ b/backend/internal/handler/rock.go @@ -416,14 +416,14 @@ func (h *RockHandler) ReleaseToken(c *gin.Context) { response.OK(c, gin.H{"message": "已释放回可用库存,该提货链接可以再次分配"}) } -// RefreshMismatchTokens 一键刷新全部绑定错误的 Token(同步执行,单次最多 20 条)。 -func (h *RockHandler) RefreshMismatchTokens(c *gin.Context) { - result, err := h.rockSvc.RefreshAllMismatchTokens(c.Request.Context()) +// RefreshStaleTokens 一键刷新异常 Token(绑定错误 + 使用超时 + 绑定超时,同步执行,单次最多 20 条)。 +func (h *RockHandler) RefreshStaleTokens(c *gin.Context) { + result, err := h.rockSvc.RefreshAllStaleTokens(c.Request.Context()) if err != nil { response.BadRequest(c, err.Error()) return } - message := fmt.Sprintf("已扫描 %d 条绑定错误 Token,修复 %d 条", result.Scanned, result.Fixed) + message := fmt.Sprintf("已扫描 %d 条异常 Token,修复 %d 条", result.Scanned, result.Fixed) if result.Failed > 0 { message += fmt.Sprintf(",失败 %d 条", result.Failed) } diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index ae45711..5d377eb 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -299,7 +299,7 @@ func Setup(h *Handlers) *gin.Engine { platform.POST("/platform/rock/tokens/:id/reset", h.Rock.ResetToken) platform.POST("/platform/rock/tokens/:id/release", h.Rock.ReleaseToken) platform.POST("/platform/rock/tokens/:id/refresh-binding", h.Rock.RefreshBinding) - platform.POST("/platform/rock/token-batches/refresh-mismatch", h.Rock.RefreshMismatchTokens) + platform.POST("/platform/rock/token-batches/refresh-stale", h.Rock.RefreshStaleTokens) platform.GET("/platform/rock/tokens/:id/package-link", h.Rock.TokenPackageLink) platform.GET("/platform/rock/token-batches/preview", h.Rock.PreviewTokenDelete) platform.POST("/platform/rock/token-batches/batch-delete", h.Rock.BatchDeleteTokens) diff --git a/backend/internal/service/rock_mismatch_scan.go b/backend/internal/service/rock_mismatch_scan.go deleted file mode 100644 index 93010be..0000000 --- a/backend/internal/service/rock_mismatch_scan.go +++ /dev/null @@ -1,113 +0,0 @@ -package service - -import ( - "context" - "errors" - "log" - "sync/atomic" - "time" - - "affiliate_dash/internal/model" -) - -const ( - // 每次巡检/一键刷新最多处理 20 条,避免手动请求超时。 - rockMismatchScanLimit = 20 - // 同一 Token 的处理冷却:10 分钟内不重复调用上游。 - rockMismatchScanCooldown = 10 * time.Minute - // 逐条刷新间隔,避免触发上游限流。 - rockMismatchScanGap = 1500 * time.Millisecond -) - -// errRockMismatchScanBusy 表示已有巡检/一键刷新在跑,本次直接跳过。 -var errRockMismatchScanBusy = errors.New("绑定错误刷新任务正在执行") - -var rockMismatchScanRunning atomic.Bool - -// RockMismatchRefreshResult 是绑定错误刷新(巡检/一键)的汇总结果。 -type RockMismatchRefreshResult struct { - Scanned int `json:"scanned"` // 本次扫描条数 - Fixed int `json:"fixed"` // 已同步为正常状态的条数 - Failed int `json:"failed"` // 调用上游失败的条数 - Remaining int `json:"remaining"` // 仍停留在绑定错误的条数 -} - -// RefreshMismatchTokens 定时巡检入口:受「自动刷新」开关控制,关闭时直接跳过。 -func (s *RockService) RefreshMismatchTokens(ctx context.Context) { - cfg, err := s.GetConfig(ctx) - if err != nil { - log.Printf("[Rock] 绑定错误巡检读取配置失败: %v", err) - return - } - if !cfg.AutoRefreshMismatchEnabled { - return - } - result, err := s.refreshMismatchTokens(ctx) - if err != nil { - if errors.Is(err, errRockMismatchScanBusy) { - return - } - log.Printf("[Rock] 绑定错误巡检失败: %v", err) - return - } - if result.Scanned == 0 { - return - } - log.Printf("[Rock] 绑定错误巡检完成: 扫描=%d 修复=%d 仍异常=%d 失败=%d", - result.Scanned, result.Fixed, result.Remaining, result.Failed) -} - -// RefreshAllMismatchTokens 手动「一键刷新绑定错误」:忽略自动开关,同步执行并返回结果。 -func (s *RockService) RefreshAllMismatchTokens(ctx context.Context) (*RockMismatchRefreshResult, error) { - return s.refreshMismatchTokens(ctx) -} - -// refreshMismatchTokens 扫描绑定错误 Token,逐条刷新上游状态。 -// -// 选取条件:status=mismatch 且 updated_at 早于 10 分钟前,按最久未处理优先,单次最多 20 条。 -// 刷新失败或上游仍未绑定时,RefreshTokenBinding 会写 updated_at,从而进入下一轮冷却。 -func (s *RockService) refreshMismatchTokens(ctx context.Context) (*RockMismatchRefreshResult, error) { - if !rockMismatchScanRunning.CompareAndSwap(false, true) { - return nil, errRockMismatchScanBusy - } - defer rockMismatchScanRunning.Store(false) - - cutoff := time.Now().Add(-rockMismatchScanCooldown) - var tokens []model.RockToken - if err := s.db.WithContext(ctx). - Where("status = ?", model.RockStatusMismatch). - Where("updated_at <= ?", cutoff). - Order("updated_at ASC"). - Limit(rockMismatchScanLimit). - Find(&tokens).Error; err != nil { - return nil, err - } - - result := &RockMismatchRefreshResult{Scanned: len(tokens)} - for i, token := range tokens { - if i > 0 { - select { - case <-ctx.Done(): - return result, ctx.Err() - case <-time.After(rockMismatchScanGap): - } - } - - res, err := s.RefreshTokenBinding(ctx, token.ID) - if err != nil { - result.Failed++ - // 失败也推进 updated_at,保证同一 Token 至少 10 分钟后才会再次尝试。 - _ = s.db.WithContext(ctx).Model(&model.RockToken{}). - Where("id = ?", token.ID). - Update("updated_at", time.Now()).Error - log.Printf("[Rock] 刷新绑定错误 Token %d 失败: %v", token.ID, err) - continue - } - if res.Status == model.RockStatusMismatch { - result.Remaining++ - continue - } - result.Fixed++ - } - return result, nil -} diff --git a/backend/internal/service/rock_overdue_test.go b/backend/internal/service/rock_overdue_test.go index 2116067..e9a0b80 100644 --- a/backend/internal/service/rock_overdue_test.go +++ b/backend/internal/service/rock_overdue_test.go @@ -98,7 +98,7 @@ func TestListTokens_OverdueFilters(t *testing.T) { } } -func TestRefreshMismatchTokens_SkipsWhenAutoRefreshDisabled(t *testing.T) { +func TestRefreshStaleTokens_SkipsWhenAutoRefreshDisabled(t *testing.T) { db := newRockTestDB(t) var calls int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -123,7 +123,7 @@ func TestRefreshMismatchTokens_SkipsWhenAutoRefreshDisabled(t *testing.T) { t.Fatalf("调整 updated_at 失败: %v", err) } - svc.RefreshMismatchTokens(context.Background()) + svc.RefreshStaleTokens(context.Background()) if atomic.LoadInt32(&calls) != 0 { t.Fatalf("自动刷新关闭时不应调用上游") @@ -137,7 +137,7 @@ func TestRefreshMismatchTokens_SkipsWhenAutoRefreshDisabled(t *testing.T) { } } -func TestRefreshAllMismatchTokens_FixesMismatch(t *testing.T) { +func TestRefreshAllStaleTokens_FixesMismatch(t *testing.T) { db := newRockTestDB(t) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -168,7 +168,7 @@ func TestRefreshAllMismatchTokens_FixesMismatch(t *testing.T) { t.Fatalf("调整 updated_at 失败: %v", err) } - result, err := svc.RefreshAllMismatchTokens(context.Background()) + result, err := svc.RefreshAllStaleTokens(context.Background()) if err != nil { t.Fatalf("一键刷新失败: %v", err) } @@ -184,3 +184,129 @@ func TestRefreshAllMismatchTokens_FixesMismatch(t *testing.T) { t.Fatalf("应修复为 bound 并清空 mismatch 标记,实际 %+v", saved) } } + +// 手动一键刷新除了绑定错误,还要覆盖「使用超时」和「绑定超时」两类 Token。 +func TestRefreshAllStaleTokens_IncludesOverdueTokens(t *testing.T) { + db := newRockTestDB(t) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/redeem/CODE-USING-OLD/refresh": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "token": map[string]interface{}{ + "status": "bound", + "bound_account": "新角色", + "bound_account_id": "10001", + }, + }) + case "/api/redeem/CODE-BOUND-OLD/refresh": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "token": map[string]interface{}{ + "status": "redeemed", + "bound_account": "旧角色", + "bound_account_id": "10002", + "redeemed_at": "2026-11-07 12:00:00", + }, + }) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + svc := NewRockService(db, NewRockClient(server.URL)) + setRockTestBearer(t, svc, "bearer-test") + + old := time.Now().AddDate(0, 0, -10) + usingToken := model.RockToken{ + UpstreamTokenID: "stale-using", + RedeemCode: "CODE-USING-OLD", + Status: model.RockStatusUsing, + UsingAt: &old, + } + boundToken := model.RockToken{ + UpstreamTokenID: "stale-bound", + RedeemCode: "CODE-BOUND-OLD", + Status: model.RockStatusBound, + BoundAt: &old, + } + for _, token := range []*model.RockToken{&usingToken, &boundToken} { + if err := db.Create(token).Error; err != nil { + t.Fatalf("创建 Token 失败: %v", err) + } + if err := db.Model(&model.RockToken{}).Where("id = ?", token.ID). + UpdateColumn("updated_at", time.Now().Add(-time.Hour)).Error; err != nil { + t.Fatalf("调整 updated_at 失败: %v", err) + } + } + + result, err := svc.RefreshAllStaleTokens(context.Background()) + if err != nil { + t.Fatalf("一键刷新失败: %v", err) + } + if result.Scanned != 2 || result.Fixed != 2 || result.Remaining != 0 || result.Failed != 0 { + t.Fatalf("一键刷新结果不符: %+v", result) + } + + var savedUsing, savedBound model.RockToken + if err := db.First(&savedUsing, usingToken.ID).Error; err != nil { + t.Fatalf("读取使用超时 Token 失败: %v", err) + } + if savedUsing.Status != model.RockStatusBound { + t.Fatalf("使用超时 Token 应同步为 bound,实际 %s", savedUsing.Status) + } + if err := db.First(&savedBound, boundToken.ID).Error; err != nil { + t.Fatalf("读取绑定超时 Token 失败: %v", err) + } + if savedBound.Status != model.RockStatusRedeemed || savedBound.RedeemedAt == nil { + t.Fatalf("绑定超时 Token 应同步为 redeemed,实际 %+v", savedBound) + } +} + +// 自动刷新与一键刷新同规则:开关开启后也要覆盖使用超时 Token。 +func TestRefreshStaleTokens_IncludesOverdueWhenEnabled(t *testing.T) { + db := newRockTestDB(t) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "token": map[string]interface{}{ + "status": "bound", + "bound_account": "自动刷新角色", + "bound_account_id": "20001", + }, + }) + })) + defer server.Close() + + svc := NewRockService(db, NewRockClient(server.URL)) + setRockTestBearer(t, svc, "bearer-test") + enabled := true + if _, err := svc.UpdateConfig(context.Background(), UpdateRockConfigReq{AutoRefreshMismatchEnabled: &enabled}); err != nil { + t.Fatalf("开启自动刷新失败: %v", err) + } + + old := time.Now().AddDate(0, 0, -10) + token := model.RockToken{ + UpstreamTokenID: "auto-stale-using", + RedeemCode: "CODE-AUTO-OLD", + Status: model.RockStatusUsing, + UsingAt: &old, + } + if err := db.Create(&token).Error; err != nil { + t.Fatalf("创建 Token 失败: %v", err) + } + if err := db.Model(&model.RockToken{}).Where("id = ?", token.ID). + UpdateColumn("updated_at", time.Now().Add(-time.Hour)).Error; err != nil { + t.Fatalf("调整 updated_at 失败: %v", err) + } + + svc.RefreshStaleTokens(context.Background()) + + var saved model.RockToken + if err := db.First(&saved, token.ID).Error; err != nil { + t.Fatalf("读取 Token 失败: %v", err) + } + if saved.Status != model.RockStatusBound { + t.Fatalf("自动刷新也应处理使用超时 Token,实际 %s", saved.Status) + } +} diff --git a/backend/internal/service/rock_stale_scan.go b/backend/internal/service/rock_stale_scan.go new file mode 100644 index 0000000..8109c6b --- /dev/null +++ b/backend/internal/service/rock_stale_scan.go @@ -0,0 +1,128 @@ +package service + +import ( + "context" + "errors" + "log" + "sync/atomic" + "time" + + "affiliate_dash/internal/model" +) + +const ( + // 每次巡检/一键刷新最多处理 20 条,避免手动请求超时。 + rockStaleScanLimit = 20 + // 同一 Token 的处理冷却:10 分钟内不重复调用上游。 + rockStaleScanCooldown = 10 * time.Minute + // 逐条刷新间隔,避免触发上游限流。 + rockStaleScanGap = 1500 * time.Millisecond +) + +// errRockStaleScanBusy 表示已有巡检/一键刷新在跑,本次直接跳过。 +var errRockStaleScanBusy = errors.New("刷新任务正在执行") + +var rockStaleScanRunning atomic.Bool + +// RockStaleRefreshResult 是异常 Token 刷新(巡检/一键)的汇总结果。 +type RockStaleRefreshResult struct { + Scanned int `json:"scanned"` // 本次扫描条数 + Fixed int `json:"fixed"` // 已同步为其它状态(视为修复)的条数 + Failed int `json:"failed"` // 调用上游失败的条数 + Remaining int `json:"remaining"` // 刷新后状态仍未变化的条数 +} + +// RefreshStaleTokens 定时巡检入口:受「自动刷新」开关控制,关闭时直接跳过。 +// +// 与手动「一键刷新」使用同一套扫描规则:绑定错误 + 使用超时 + 绑定超时。 +func (s *RockService) RefreshStaleTokens(ctx context.Context) { + cfg, err := s.GetConfig(ctx) + if err != nil { + log.Printf("[Rock] 异常 Token 巡检读取配置失败: %v", err) + return + } + if !cfg.AutoRefreshMismatchEnabled { + return + } + result, err := s.refreshStaleTokens(ctx) + if err != nil { + if errors.Is(err, errRockStaleScanBusy) { + return + } + log.Printf("[Rock] 异常 Token 巡检失败: %v", err) + return + } + if result.Scanned == 0 { + return + } + log.Printf("[Rock] 异常 Token 巡检完成: 扫描=%d 修复=%d 仍异常=%d 失败=%d", + result.Scanned, result.Fixed, result.Remaining, result.Failed) +} + +// RefreshAllStaleTokens 手动「一键刷新」:绑定错误 + 使用超时 + 绑定超时, +// 忽略自动开关,同步执行并返回结果。 +func (s *RockService) RefreshAllStaleTokens(ctx context.Context) (*RockStaleRefreshResult, error) { + return s.refreshStaleTokens(ctx) +} + +// refreshStaleTokens 扫描异常 Token,逐条刷新上游状态。 +// +// 选取条件:绑定错误,或使用中/已绑定超过阈值(与列表筛选同口径); +// 且 updated_at 早于 10 分钟前(同一 Token 冷却),按最久未处理优先,单次最多 20 条。 +// 刷新失败或状态未变化时,RefreshTokenBinding 会写 updated_at,从而进入下一轮冷却。 +func (s *RockService) refreshStaleTokens(ctx context.Context) (*RockStaleRefreshResult, error) { + if !rockStaleScanRunning.CompareAndSwap(false, true) { + return nil, errRockStaleScanBusy + } + defer rockStaleScanRunning.Store(false) + + cfg, err := s.GetConfig(ctx) + if err != nil { + return nil, err + } + warnDays, _, boundWarnDays := rockOverdueThresholds(cfg) + + now := time.Now() + cutoff := now.Add(-rockStaleScanCooldown) + query := s.db.WithContext(ctx).Model(&model.RockToken{}). + Where("updated_at <= ?", cutoff). + Where( + "(status = ? OR (status = ? AND using_at IS NOT NULL AND using_at <= ?) OR (status = ? AND bound_at IS NOT NULL AND bound_at <= ?))", + model.RockStatusMismatch, + model.RockStatusUsing, rockOverdueCutoff(now, warnDays), + model.RockStatusBound, rockOverdueCutoff(now, boundWarnDays), + ) + + var tokens []model.RockToken + if err := query.Order("updated_at ASC").Limit(rockStaleScanLimit).Find(&tokens).Error; err != nil { + return nil, err + } + + result := &RockStaleRefreshResult{Scanned: len(tokens)} + for i, token := range tokens { + if i > 0 { + select { + case <-ctx.Done(): + return result, ctx.Err() + case <-time.After(rockStaleScanGap): + } + } + + res, err := s.RefreshTokenBinding(ctx, token.ID) + if err != nil { + result.Failed++ + // 失败也推进 updated_at,保证同一 Token 至少 10 分钟后才会再次尝试。 + _ = s.db.WithContext(ctx).Model(&model.RockToken{}). + Where("id = ?", token.ID). + Update("updated_at", time.Now()).Error + log.Printf("[Rock] 刷新异常 Token %d 失败: %v", token.ID, err) + continue + } + if res.Status == token.Status { + result.Remaining++ + continue + } + result.Fixed++ + } + return result, nil +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 36ed211..32cd879 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -785,7 +785,7 @@ export interface RockTokenRestoreResult { skipped_items: RockTokenRestoreSkipItem[]; } -export interface RockMismatchRefreshResult { +export interface RockStaleRefreshResult { scanned: number; fixed: number; failed: number; @@ -867,10 +867,10 @@ export const rockApi = { page_size: number; }, ), - refreshMismatchTokens: () => + refreshStaleTokens: () => request - .post("/platform/rock/token-batches/refresh-mismatch") - .then((r) => r.data.data as RockMismatchRefreshResult), + .post("/platform/rock/token-batches/refresh-stale") + .then((r) => r.data.data as RockStaleRefreshResult), previewTokenDelete: (params?: { status?: string; search?: string }) => request .get("/platform/rock/token-batches/preview", { params }) diff --git a/frontend/src/pages/PlatformRock.tsx b/frontend/src/pages/PlatformRock.tsx index ec70dfc..5b8563f 100644 --- a/frontend/src/pages/PlatformRock.tsx +++ b/frontend/src/pages/PlatformRock.tsx @@ -38,7 +38,7 @@ export default function PlatformRock() { const [togglingAutoPurchase, setTogglingAutoPurchase] = useState(false) const [autoRefreshMismatchEnabled, setAutoRefreshMismatchEnabled] = useState(false) const [togglingAutoRefresh, setTogglingAutoRefresh] = useState(false) - const [refreshingMismatch, setRefreshingMismatch] = useState(false) + const [refreshingStale, setRefreshingStale] = useState(false) const [purchasing, setPurchasing] = useState(false) const [purchaseModalOpen, setPurchaseModalOpen] = useState(false) const [purchaseCount, setPurchaseCount] = useState(50) @@ -177,13 +177,13 @@ export default function PlatformRock() { } } - // 一键刷新全部绑定错误的 Token:同步执行,单次最多 20 条 - const handleRefreshMismatch = async () => { - setRefreshingMismatch(true) + // 一键刷新异常 Token:绑定错误 + 使用超时 + 绑定超时,同步执行,单次最多 20 条 + const handleRefreshStale = async () => { + setRefreshingStale(true) try { - const res = await rockApi.refreshMismatchTokens() + const res = await rockApi.refreshStaleTokens() if (res.scanned === 0) { - message.info('暂无可刷新的绑定错误 Token(10 分钟内已刷新过的会自动跳过)') + message.info('暂无可刷新的异常 Token(10 分钟内已刷新过的会自动跳过)') } else if (res.failed > 0 || res.remaining > 0) { message.warning(res.message) } else { @@ -192,13 +192,13 @@ export default function PlatformRock() { loadData() loadTokens() } catch (e) { - message.error('刷新绑定错误失败: ' + (e instanceof Error ? e.message : '未知错误')) + message.error('一键刷新失败: ' + (e instanceof Error ? e.message : '未知错误')) } finally { - setRefreshingMismatch(false) + setRefreshingStale(false) } } - // 自动刷新绑定错误开关:切换即保存,后端每 5 分钟巡检一次 + // 自动刷新开关:与「一键刷新」同规则(绑定错误 + 使用超时 + 绑定超时),切换即保存 const handleToggleAutoRefreshMismatch = async (checked: boolean) => { const previous = autoRefreshMismatchEnabled setAutoRefreshMismatchEnabled(checked) @@ -208,7 +208,7 @@ export default function PlatformRock() { setConfig(updated) setAutoRefreshMismatchEnabled(updated.auto_refresh_mismatch_enabled) message.success( - checked ? '自动刷新已开启:每 5 分钟最多刷新 20 条绑定错误 Token' : '自动刷新已关闭', + checked ? '自动刷新已开启:每 5 分钟按同样规则刷新最多 20 条异常 Token' : '自动刷新已关闭', ) } catch (e) { setAutoRefreshMismatchEnabled(previous) @@ -476,12 +476,12 @@ export default function PlatformRock() { totalTokens={totalTokens} loadingTokens={loadingTokens} autoRefreshMismatchEnabled={autoRefreshMismatchEnabled} - refreshingMismatch={refreshingMismatch} + refreshingStale={refreshingStale} togglingAutoRefresh={togglingAutoRefresh} onSearchKeywordChange={setSearchKeyword} onFilterStatusChange={handleFilterStatusChange} onRefresh={loadTokens} - onRefreshMismatch={handleRefreshMismatch} + onRefreshStale={handleRefreshStale} onToggleAutoRefreshMismatch={handleToggleAutoRefreshMismatch} /> diff --git a/frontend/src/pages/platformRock/RockTokenFilterBar.tsx b/frontend/src/pages/platformRock/RockTokenFilterBar.tsx index bb2904a..728a5cb 100644 --- a/frontend/src/pages/platformRock/RockTokenFilterBar.tsx +++ b/frontend/src/pages/platformRock/RockTokenFilterBar.tsx @@ -1,5 +1,5 @@ import { Badge, Button, Card, Input, Radio, Space, Switch, Tooltip, Typography } from 'antd' -import { ReloadOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons' +import { ReloadOutlined, QuestionCircleOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons' import type { RockStats } from '../../api' const { Text } = Typography @@ -11,12 +11,12 @@ export interface RockTokenFilterBarProps { totalTokens: number loadingTokens: boolean autoRefreshMismatchEnabled: boolean - refreshingMismatch: boolean + refreshingStale: boolean togglingAutoRefresh: boolean onSearchKeywordChange: (value: string) => void onFilterStatusChange: (value: string) => void onRefresh: () => void - onRefreshMismatch: () => void + onRefreshStale: () => void onToggleAutoRefreshMismatch: (checked: boolean) => void } @@ -27,12 +27,12 @@ export function RockTokenFilterBar({ totalTokens, loadingTokens, autoRefreshMismatchEnabled, - refreshingMismatch, + refreshingStale, togglingAutoRefresh, onSearchKeywordChange, onFilterStatusChange, onRefresh, - onRefreshMismatch, + onRefreshStale, onToggleAutoRefreshMismatch, }: RockTokenFilterBarProps) { return ( @@ -109,15 +109,24 @@ export function RockTokenFilterBar({ - - - + + 同时刷新三类异常 Token:绑定错误、使用超时、绑定超时。 +
+ 单次最多 20 条,逐条间隔 1.5 秒,同一 Token 10 分钟内不重复刷新。 +
+ 「启用自动刷新」每 5 分钟按同样规则自动执行。 + + } + > + +
+