## 痛点定位 - 新用户接口在单次请求内对每个用户做多次单表查询(资产、道具卡、优惠券、称号、最后在线时间),形成 N+1 查询,延迟随列表大小线性增加(`internal/api/admin/dashboard_admin.go` 的 `DashboardNewUsers()`)。 - 实时抽奖动态接口对每条抽奖日志逐条联查用户/期/活动/奖品,亦为 N+1 查询(`DashboardDrawStream()`)。 ## 后端优化方案 - 批量聚合替代逐条查询(NewUsers) - 第一步:一次查出当前页的 `user_id` 列表 - 第二步:分别对各表做分组聚合(单次查询): - 资产:`SELECT user_id, COUNT(*) FROM user_inventory WHERE user_id IN (...) GROUP BY user_id` - 道具卡:`SELECT user_id, COUNT(*) FROM user_item_cards WHERE status=1 AND user_id IN (...) GROUP BY user_id` - 优惠券:`SELECT user_id, COUNT(*) FROM user_coupons WHERE user_id IN (...) GROUP BY user_id` - 称号:`SELECT ut.user_id, st.id, st.name FROM user_titles ut LEFT JOIN system_titles st ON st.id=ut.title_id WHERE ut.user_id IN (...)` - 最后在线:分别取各行为表 `MAX(time)` 按 `user_id` 聚合,再在内存求最大值 - 积分余额:改为批量查 `user_points` 有效积分 `GROUP BY user_id`,或接入预聚合表(见下) - 第三步:用 `map[user_id]value` 合并到用户列表,避免每行多次往返数据库 - 连接查询替代逐条补全(DrawStream) - 单条 SQL 联查:`activity_draw_logs` LEFT JOIN `users`、`activity_issues`、`activities`、`activity_reward_settings`,一次性返回 `nickname/activityName/issueNumber/prizeName` - 保留 `since_id + limit` 增量拉取;避免循环内 `First()` 调用 - 预聚合与缓存 - 建议增加 `user_stats` 表(或 Redis 缓存)维护:`points_balance`、`inventory_count`、`item_card_count`、`coupon_count`、`title_list`、`last_online_at` - 更新策略: - 同步:在相关写入路径(发放积分/道具卡/优惠券/称号、抽奖、下单)更新统计 - 异步:crontab 每 1-5 分钟增量刷新 - 实时抽奖:为最近 50 条结果加 3-5 秒内存缓存(LRU 或 Redis) - 限流与分页 - 新用户默认 `page_size=20`,最大 50;实时抽奖 `limit<=100` - 对于“今年”范围下分页检索控制页大小,避免一次返回过多用户 ## 数据库与索引 - 新建/确认索引: - `users(created_at)`、`users(id)` - `activity_draw_logs(id DESC, user_id, issue_id, reward_id, created_at)` - `user_inventory(user_id)`、`user_item_cards(user_id,status)`、`user_coupons(user_id)`、`user_titles(user_id,title_id)` - `user_points(user_id, valid_end)`、`user_points_ledger(user_id, created_at)` - 可选:为 `log_request(path, created_at)` 增加 `user_id` 字段与索引,精确“最后在线时间” ## 前端协同优化 - 新用户页签切换时:防抖 200ms;保留上次结果并显示加载骨架,避免空白闪烁 - 实时抽奖轮询:保持 5s;追加条目后裁剪到 100-200 条以保证 DOM 轻量;使用 `ref` 持有列表(已改) - 宽度问题:动态项允许换行并分两行展示(已改),避免不可见 ## 验收指标 - 新用户接口:在 `page_size=20` 时 P95 响应时间 < 200ms(本地数据量下) - 实时抽奖接口:在 `limit=50` 时 P95 响应时间 < 150ms;每轮轮询端到端显示时间 < 300ms ## 下一步实现内容(获批后执行) 1) 重写 `DashboardNewUsers()` 为批量聚合与合并映射 2) 重写 `DashboardDrawStream()` 为单次 LEFT JOIN 联查 3) 添加必要索引迁移脚本 4) 可选:落地 `user_stats` 预聚合与写路径刷新机制 确认后我将按上述方案逐条落地并提供压测数据与对比报告。