## 当前情况 - 管理端“工作台”路由:`web/admin/src/router/modules/dashboard.ts:3-24` - 工作台页面:`web/admin/src/views/dashboard/console/index.vue` - 工作台数据源:`web/admin/src/api/dashboard.ts` 全为本地 Mock,无真实后端调用 - 后端已存在管理端认证路由分组:`internal/router/router.go:55-151`,但未注册任何“dashboard”端点 ## 技术栈与约束 - 后端:Gin + GORM Gen,入口 `main.go:52-67`,路由 `internal/router/router.go` - 数据模型可用:`internal/repository/mysql/model/*.gen.go`(如 `users`、`activity_draw_logs`、`user_item_cards`、`user_points`、`guild_members`) - 认证:管理端统一走 `Authorization` 头(`LoginVerifyToken`),同组复用:`internal/router/router.go:56` ## 端点设计(前缀 `/api/admin/dashboard`) 1) `GET /cards` - 入参:`rangeType=today|7d|30d|custom`,可选 `start=YYYY-MM-DD&end=YYYY-MM-DD`(custom 时) - 出参:`CardStat`(与 `web/admin/src/api/dashboard.ts` 一致) - 统计口径: - `itemCardSales`:`user_item_cards.created_at` 范围新增数(`user_item_cards`) - `drawCount`:`activity_draw_logs.created_at` 范围次数(`activity_draw_logs`) - `newUsers`:`users.created_at` 范围新增数(`users`) - `totalPoints`:全量有效积分总和(`user_points.points` 过滤 `valid_end` 未过期),实现参考 `internal/service/user/points_balance.go:8-21` - 环比字段 `*_Change`:与上一等长时间窗口对比百分比(向下取整),无前窗则返回 `+0%` 2) `GET /user_trend` - 入参:`rangeType`、`granularity=day|week|month` - 出参:`UserTrendResp` - 统计:按粒度聚合 `users.created_at` 计数生成时间序列 3) `GET /draw_trend` - 入参:同上 - 出参:`DrawTrendResp` - 统计:按粒度聚合 `activity_draw_logs.created_at` 计数生成时间序列 4) `GET /new_users` - 入参:`page`、`page_size` - 出参:`NewUserListResp` - 明细项字段: - `pointsBalance`:用户有效积分余额(复用 `GetPointsBalance` 逻辑 `internal/service/user/points_balance.go:8-21`) - `inventoryCount`:`user_inventory` 记录数(状态过滤视业务) - `itemCardCount`:`user_item_cards` 有效未使用数量(`status=1`) 5) `GET /draw_stream` - 入参:`since_id`(可选)、`limit`(默认 50,最大 100) - 出参:`DrawStreamResp` - 统计:按 `id` 递减拉取最近抽奖日志,关联 `users.nickname`、`activity_issues.name` 生成展示项;返回 `sinceId = max(id)` 以便前端轮询增量 6) `GET /todos` - 入参:`limit`(默认 50,最大 100) - 出参:`TodoListResp` - 规则: - `bind_mobile`:`users.mobile IS NULL OR ''` - `join_guild`:`NOT EXISTS guild_members WHERE user_id=users.id AND status=1` - 返回 `avatar`、`nickname`、`taskLabel` 友好文案 ## 后端改动点 - 新增处理器:`internal/api/admin/dashboard_admin.go` - 采用现有 handler 模式:`internal/api/admin/admin.go:16-42` - 为每个端点提供 `core.HandlerFunc`,入参校验使用 `ShouldBindForm/JSON` 与 `internal/pkg/validation` - 新增服务层:`internal/service/admin/dashboard_*.go` - `GetCardStats(ctx, range)`, `GetUserTrend(ctx, range, granularity)`, `GetDrawTrend(...)` - `ListNewUsersWithStats(ctx, page, pageSize)`:多表聚合(users + user_points + user_item_cards + user_inventory) - `ListDrawStream(ctx, sinceID, limit)`:抽奖日志联表 users、activity_issues - `ListTodos(ctx, limit)`:基于 users 与 guild_members 规则 - 路由注册:在管理端认证组添加 - 文件:`internal/router/router.go` - 组:`adminAuthApiRouter`(`internal/router/router.go:55-151`) - 注册形如: - `adminAuthApiRouter.GET("/dashboard/cards", adminHandler.DashboardCards())` - `adminAuthApiRouter.GET("/dashboard/user_trend", adminHandler.DashboardUserTrend())` - `adminAuthApiRouter.GET("/dashboard/draw_trend", adminHandler.DashboardDrawTrend())` - `adminAuthApiRouter.GET("/dashboard/new_users", adminHandler.DashboardNewUsers())` - `adminAuthApiRouter.GET("/dashboard/draw_stream", adminHandler.DashboardDrawStream())` - `adminAuthApiRouter.GET("/dashboard/todos", adminHandler.DashboardTodos())` ## 前端改动点 - 更新 `web/admin/src/api/dashboard.ts` - 用 `request.get` 替换 Mock: - `fetchCardStats(range)` → `GET admin/dashboard/cards` - `fetchUserTrend(range, granularity)` → `GET admin/dashboard/user_trend` - `fetchDrawTrend(range, granularity)` → `GET admin/dashboard/draw_trend` - `fetchNewUsers(page, pageSize)` → `GET admin/dashboard/new_users` - `fetchDrawStream(sinceId, limit)` → `GET admin/dashboard/draw_stream` - `fetchTodos(limit)` → `GET admin/dashboard/todos` - 保持 TS 接口不变,便于无缝替换 ## 数据来源与代码参考 - 路由分组:`internal/router/router.go:55-61` - 抽奖日志分页示例:`internal/service/activity/draw_logs_list.go:9-28` - 用户积分余额:`internal/service/user/points_balance.go:8-21` - 用户综合统计:`internal/service/user/stats.go:13-38` - 数据模型表:`internal/repository/mysql/model/*.gen.go`(如 `users.gen.go:15-29`、`activity_draw_logs.gen.go:13-24`、`user_item_cards.gen.go:13-28`、`guild_members.gen.go:13-23`) ## 接口示例返回 - `GET /api/admin/dashboard/cards`: ```json { "itemCardSales": 1234, "drawCount": 5678, "newUsers": 321, "totalPoints": 98765, "itemCardChange": "+12%", "drawChange": "+8%", "newUserChange": "+5%", "pointsChange": "+3%" } ``` - `GET /api/admin/dashboard/user_trend` / `draw_trend`:`{ "granularity": "day", "list": [{"date":"2025-11-01","value":100}, ...] }` - 其他返回与 `web/admin/src/api/dashboard.ts` 对齐 ## 验证与测试 - Swagger 注解与分组标签:沿用现有风格(参考 `internal/api/admin/users_admin.go:30-46`) - 集成测试:参考 `internal/api/admin/titles_admin_test.go` 新增 API 测试用例(登录获取 Token 后调用) - 手动验证: - 后端:`curl -H "Authorization: " "http://localhost:/api/admin/dashboard/cards?rangeType=7d"` - 前端:工作台各模块正常展示且不再使用随机数据 ## 性能与索引建议 - 为 `users.created_at`、`activity_draw_logs.created_at`、`user_item_cards.created_at` 建立索引 - 汇总接口注意分页与时间窗口限制,默认范围 7 天,可配置上限 30 天 ## 交付范围与验收 - 完成 6 个端点后,前端工作台所有模块均有真实数据源 - 验收:页面展示与接口返回满足 TS 类型;Swagger 文档可用;管理员认证校验生效 确认后我将: - 在后端新增处理器与服务实现并注册路由 - 替换前端 `dashboard.ts` 的 Mock 为真实请求 - 补充必要的 Swagger 注释与最小测试用例