feat(admin): 用量记录页新增"用户 Token 排行"面板

- 在 /admin/usage 新增按用户聚合的 Token 排行表(输入/输出/缓存/总 Token、请求数、费用)
- 支持按列排序、邮箱搜索、Top N (20/50/100/200),点击行下钻到该用户
- 复用现有筛选(用户/时间/模型/分组等)与既有 KPI 卡片、逐条日志
- 后端对 /admin/dashboard/user-breakdown 做加法扩展(向后兼容):
  - UserBreakdownItem 增加 input_tokens/output_tokens/cache_tokens
  - 新增 sort_by(白名单排序,防注入,缺省仍按 actual_cost)
- 新增 sort_by 转发单元测试;更新 UsageView.spec 稳定桩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Birditch
2026-07-09 06:04:22 -04:00
co-authored by Claude Opus 4.8
parent 64fdc11ec4
commit b062b36646
11 changed files with 277 additions and 11 deletions
@@ -59,7 +59,21 @@ func TestGetUserBreakdown_GroupIDFilter(t *testing.T) {
require.Equal(t, int64(42), repo.capturedDim.GroupID)
require.Empty(t, repo.capturedDim.Model)
require.Empty(t, repo.capturedDim.Endpoint)
require.Equal(t, 50, repo.capturedLimit) // default limit
require.Equal(t, 50, repo.capturedLimit) // default limit
require.Empty(t, repo.capturedDim.SortBy) // no sort_by => empty (repo falls back to default)
}
func TestGetUserBreakdown_SortBy(t *testing.T) {
repo := &userBreakdownRepoCapture{}
router := newUserBreakdownRouter(repo)
req := httptest.NewRequest(http.MethodGet,
"/admin/dashboard/user-breakdown?start_date=2026-03-01&end_date=2026-03-16&sort_by=total_tokens", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.Equal(t, "total_tokens", repo.capturedDim.SortBy)
}
func TestGetUserBreakdown_ModelFilter(t *testing.T) {