From 6a655d0ce2b471d660b0b7614bdc5ae44c9770bd Mon Sep 17 00:00:00 2001 From: Wang Zhuoxuan Date: Mon, 11 May 2026 18:18:33 +0800 Subject: [PATCH] Add weekly XP schema --- docs/gamification-server-plan.md | 2 +- src/db/schema.ts | 34 +++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/gamification-server-plan.md b/docs/gamification-server-plan.md index c70385b..569ff78 100644 --- a/docs/gamification-server-plan.md +++ b/docs/gamification-server-plan.md @@ -34,7 +34,7 @@ | G0-3 | 新增钱包和道具库存模型 | [x] | 支持金币余额、道具库存、道具获得/消耗流水 | | G0-4 | 新增奖励流水模型 | [x] | 记录奖励来源、幂等 key、奖励快照、发放前后状态 | | G0-5 | 新增每日任务或每日进度模型 | [x] | 可统计每日首组挑战、每日任务完成、每日高奖励次数 | -| G0-6 | 新增周 XP 统计模型或扩展周榜快照 | [ ] | 可按自然周统计 XP,支持每周一刷新和历史快照 | +| G0-6 | 新增周 XP 统计模型或扩展周榜快照 | [x] | 可按自然周统计 XP,支持每周一刷新和历史快照 | | G0-7 | 生成并提交数据库迁移 | [ ] | `db/migrations/` 包含 schema 变更,迁移文件不被 `.gitignore` 遗漏 | ## Phase G1:挑战组与答题结算 diff --git a/src/db/schema.ts b/src/db/schema.ts index 7045274..18e4f27 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -396,12 +396,40 @@ export const leaderboardSnapshots = mysqlTable('leaderboard_snapshots', { tier: mysqlEnum('tier', ['bronze', 'silver', 'gold', 'platinum', 'diamond', 'master', 'grandmaster', 'champion', 'legend', 'mythic']).notNull(), // 排行榜段位。 weeklyXp: int('weekly_xp').default(0), // 本周累计经验值。 rank: int('rank'), // 当前排名。 + groupId: varchar('group_id', { length: 80 }), // 周榜分组 ID。 league: varchar('league', { length: 50 }), // 所属联赛或榜单分组。 - weekStart: date('week_start'), // 统计周开始日期。 - weekEnd: date('week_end'), // 统计周结束日期。 + rewardSnapshot: json('reward_snapshot').$type>(), // 周结算奖励预览或实际发放快照。 + settledAt: datetime('settled_at'), // 周榜结算时间。 + weekStart: date('week_start').notNull(), // 统计周开始日期,按 UTC 自然周一。 + weekEnd: date('week_end').notNull(), // 统计周结束日期,按 UTC 自然周日。 createdAt: datetime('created_at').default(sql`CURRENT_TIMESTAMP`), // 创建时间。 }, (table) => [ - index('idx_user_week').on(table.userId, table.weekStart), + uniqueIndex('uk_leaderboard_snapshot_user_week').on(table.userId, table.weekStart), + index('idx_leaderboard_snapshot_group_rank').on(table.groupId, table.weekStart, table.rank), +]); + +// 用户每周 XP 统计,作为当前周排行榜和历史快照的累计数据源。 +export const userWeeklyXp = mysqlTable('user_weekly_xp', { + id: char('id', { length: 36 }).primaryKey(), + userId: char('user_id', { length: 36 }).notNull(), + weekStart: date('week_start').notNull(), // UTC 自然周一。 + weekEnd: date('week_end').notNull(), // UTC 自然周日。 + timezone: varchar('timezone', { length: 50 }).default('UTC'), // 周期展示时区。 + xpEarned: int('xp_earned').default(0), // 本周累计 XP。 + challengeSessionsCompleted: int('challenge_sessions_completed').default(0), // 本周完成挑战组数。 + groupId: varchar('group_id', { length: 80 }), // 分配到的周榜分组 ID。 + rank: int('rank'), // 当前组内排名缓存。 + settled: tinyint('settled').default(0), // 本周是否已完成结算。 + settledAt: datetime('settled_at'), // 结算时间。 + lastXpAt: datetime('last_xp_at'), // 最近一次 XP 累加时间。 + nextRefreshAt: datetime('next_refresh_at'), // 下一次周榜刷新时间。 + createdAt: datetime('created_at').default(sql`CURRENT_TIMESTAMP`), // 创建时间。 + updatedAt: datetime('updated_at').default(sql`CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`), // 更新时间。 +}, (table) => [ + uniqueIndex('uk_weekly_xp_user_week').on(table.userId, table.weekStart), + index('idx_weekly_xp_group_rank').on(table.groupId, table.weekStart, table.xpEarned), + index('idx_weekly_xp_week_settled').on(table.weekStart, table.settled), + foreignKey({ columns: [table.userId], foreignColumns: [users.id] }), ]); // ── Subscriptions ──────────────────────────────────────────────────