diff --git a/docs/gamification-server-plan.md b/docs/gamification-server-plan.md index 126f4c9..1180277 100644 --- a/docs/gamification-server-plan.md +++ b/docs/gamification-server-plan.md @@ -32,7 +32,7 @@ | G0-1 | 梳理游戏化规则常量模块 | [x] | 新增集中规则定义,覆盖红心、挑战组、XP、等级、金币、道具、广告恢复、周榜周期 | | G0-2 | 新增挑战组数据模型 | [x] | 支持 challenge session、session answers、组状态、正确数、完成时间、幂等提交 | | G0-3 | 新增钱包和道具库存模型 | [x] | 支持金币余额、道具库存、道具获得/消耗流水 | -| G0-4 | 新增奖励流水模型 | [ ] | 记录奖励来源、幂等 key、奖励快照、发放前后状态 | +| G0-4 | 新增奖励流水模型 | [x] | 记录奖励来源、幂等 key、奖励快照、发放前后状态 | | G0-5 | 新增每日任务或每日进度模型 | [ ] | 可统计每日首组挑战、每日任务完成、每日高奖励次数 | | G0-6 | 新增周 XP 统计模型或扩展周榜快照 | [ ] | 可按自然周统计 XP,支持每周一刷新和历史快照 | | G0-7 | 生成并提交数据库迁移 | [ ] | `db/migrations/` 包含 schema 变更,迁移文件不被 `.gitignore` 遗漏 | diff --git a/src/db/schema.ts b/src/db/schema.ts index 474fb07..8ce810a 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -262,6 +262,31 @@ export const inventoryTransactions = mysqlTable('inventory_transactions', { foreignKey({ columns: [table.inventoryItemId], foreignColumns: [userInventoryItems.id] }), ]); +// ── Reward Ledger ───────────────────────────────────────────────── + +// 统一奖励结算流水,记录奖励来源、幂等边界、快照和发放前后状态。 +export const rewardLedger = mysqlTable('reward_ledger', { + id: char('id', { length: 36 }).primaryKey(), + userId: char('user_id', { length: 36 }).notNull(), + sourceType: mysqlEnum('source_type', ['challenge_answer', 'challenge_completion', 'daily_task', 'streak_milestone', 'level_up', 'theme_node', 'knowledge_card', 'chest', 'shop_purchase', 'ad_recovery', 'leaderboard_settlement', 'subscription', 'admin_grant', 'system_adjust']).notNull(), // 奖励来源。 + sourceId: varchar('source_id', { length: 120 }), // 来源业务 ID。 + idempotencyKey: varchar('idempotency_key', { length: 160 }).notNull(), // 奖励结算幂等 key。 + status: mysqlEnum('status', ['pending', 'settling', 'completed', 'failed', 'reversed']).default('pending'), // 奖励结算状态。 + rewardSnapshot: json('reward_snapshot').$type>().notNull(), // 计划发放或已发放的奖励快照。 + resourceDeltas: json('resource_deltas').$type>(), // XP、金币、红心、道具等资源变化。 + stateBefore: json('state_before').$type>(), // 发放前用户资源状态。 + stateAfter: json('state_after').$type>(), // 发放后用户资源状态。 + failureReason: varchar('failure_reason', { length: 120 }), // 结算失败或回滚原因。 + settledAt: datetime('settled_at'), // 奖励完成结算时间。 + createdAt: datetime('created_at').default(sql`CURRENT_TIMESTAMP`), // 创建时间。 + updatedAt: datetime('updated_at').default(sql`CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`), // 更新时间。 +}, (table) => [ + uniqueIndex('uk_reward_ledger_user_idempotency').on(table.userId, table.idempotencyKey), + index('idx_reward_ledger_user_status_created').on(table.userId, table.status, table.createdAt), + index('idx_reward_ledger_source').on(table.sourceType, table.sourceId), + foreignKey({ columns: [table.userId], foreignColumns: [users.id] }), +]); + // ── Question Ratings ────────────────────────────────────────────── // 用户对题目的好坏反馈数据。