@@ -1,15 +1,15 @@
|
||||
# 资产生成叙世币消耗接入方案
|
||||
# 资产操作叙世币消耗接入方案
|
||||
|
||||
## 背景
|
||||
|
||||
当前叙世币钱包余额、充值流水与邀请奖励已经收口到 `server-rs/crates/spacetime-module/src/runtime/profile.rs`。资产图片生成由 Axum API 调用外部模型并写入 OSS,SpacetimeDB reducer/procedure 不能直接执行外部网络生成,因此扣费需要拆成两层:
|
||||
当前叙世币钱包余额、充值流水与邀请奖励已经收口到 `server-rs/crates/spacetime-module/src/runtime/profile.rs`。资产图片生成和作品发布由 Axum API 调用外部模型或写入业务状态,SpacetimeDB reducer/procedure 不能直接执行外部网络生成,因此计费需要拆成两层:
|
||||
|
||||
- SpacetimeDB 负责钱包余额和流水的原子变更。
|
||||
- Axum 负责在发起外部生成前扣费,并在生成或持久化失败时补偿退款。
|
||||
- Axum 资产操作服务负责在执行业务资产操作前扣费,并在生成、持久化或发布失败时补偿退款。
|
||||
|
||||
## 首期范围
|
||||
|
||||
首期接入带 Bearer 身份、能明确归属真实用户的资产生成与发布入口:
|
||||
首期接入带 Bearer 身份、能明确归属真实用户的资产操作入口:
|
||||
|
||||
- `POST /api/custom-world/scene-image`
|
||||
- `POST /api/custom-world/cover-image`
|
||||
@@ -26,28 +26,27 @@
|
||||
- 旧资产工坊角色主形象/动作生成接口:当前仍使用 `asset-tool` 作为兼容归属,无法确认真实用户。
|
||||
- 手动上传封面:不调用外部生成模型,不消耗叙世币。
|
||||
- 自定义世界草稿自动补图链路:属于后台补全流程,避免一次用户操作触发多笔不可预期扣费。
|
||||
- 文本实体、NPC 生成:本次需求聚焦资产生成,首期只覆盖图片资产。
|
||||
- 文本实体、NPC 生成:本次需求聚焦图片资产和发布资产操作,首期只覆盖可明确归属的入口。
|
||||
|
||||
## 计费规则
|
||||
|
||||
- 每次图片资产生成请求消耗 `1` 枚叙世币。
|
||||
- 每次作品发布请求消耗 `1` 枚叙世币;余额不足时禁止发布。
|
||||
- 在调用外部图片生成前预扣,余额不足时直接返回业务错误,不调用外部模型。
|
||||
- 发布请求在写入发布状态前预扣,余额不足时直接返回业务错误,不调用发布 mutation。
|
||||
- 如果图片生成、远程下载、OSS 写入、资产记录确认或发布 mutation 失败,Axum 自动发起同额退款。
|
||||
- 每次可计费资产操作消耗 `1` 枚叙世币。
|
||||
- 图片生成和作品发布都按资产操作计费;余额不足时禁止继续执行。
|
||||
- 在调用外部图片生成或发布 mutation 前预扣,余额不足时直接返回业务错误,不继续调用后续资产操作。
|
||||
- 如果图片生成、远程下载、OSS 写入、资产记录确认或发布 mutation 失败,资产操作服务自动发起同额退款。
|
||||
- 如果退款失败,原始错误仍返回给调用方,同时服务端日志记录退款失败,便于后续人工核对。
|
||||
|
||||
## 钱包流水
|
||||
|
||||
新增两个流水来源类型,首期同时覆盖“资产生成”和“资产发布”这两类资产操作:
|
||||
公开两个流水来源类型,统一覆盖“资产生成”和“资产发布”这两类资产操作:
|
||||
|
||||
- `asset_generation_consume`:资产生成预扣,`amount_delta = -1`。
|
||||
- `asset_generation_refund`:资产生成失败退款,`amount_delta = +1`。
|
||||
- `asset_operation_consume`:资产操作预扣,`amount_delta = -1`。
|
||||
- `asset_operation_refund`:资产操作失败退款,`amount_delta = +1`。
|
||||
|
||||
`wallet_ledger_id` 由 Axum 传入,格式:
|
||||
|
||||
- 扣费:`asset_generation_consume:{user_id}:{asset_kind}:{asset_id}`
|
||||
- 退款:`asset_generation_refund:{user_id}:{asset_kind}:{asset_id}`
|
||||
- 扣费:`asset_operation_consume:{user_id}:{asset_kind}:{asset_id}`
|
||||
- 退款:`asset_operation_refund:{user_id}:{asset_kind}:{asset_id}`
|
||||
|
||||
SpacetimeDB procedure 对 `ledger_id` 做幂等保护:如果同一个流水 ID 已存在,则直接返回当前钱包快照,不重复变更余额。
|
||||
|
||||
@@ -56,9 +55,10 @@ SpacetimeDB procedure 对 `ledger_id` 做幂等保护:如果同一个流水 ID
|
||||
- `module-runtime`:新增钱包调整输入、钱包调整结果、流水来源枚举。
|
||||
- `spacetime-module`:新增 `consume_profile_wallet_points_and_return` 与 `refund_profile_wallet_points_and_return` procedure,并扩展钱包变更 helper 支持负数。
|
||||
- `spacetime-client`:新增对应调用方法和绑定类型。
|
||||
- `api-server`:在自定义世界图片生成与发布入口前扣费,错误分支退款。
|
||||
- `api-server`:资产操作服务提供统一可计费执行入口,自定义世界、Big Fish、Puzzle 业务 handler 只声明资产操作,不直接调用钱包扣费或退款。
|
||||
- `shared-contracts`:新增 API 流水来源常量,保证“我的-钱包流水”输出使用稳定契约字符串。
|
||||
- `packages/shared` 与前端:统一使用 `asset_operation_consume` / `asset_operation_refund` 展示钱包流水。
|
||||
|
||||
## 非目标
|
||||
|
||||
本次不做分档价格、不做会员免扣、不做前端计费展示改造,也不迁移旧 `server-node` 逻辑。旧资产工坊角色主形象/动作生成与发布接口仍需要先补齐 Bearer 身份归属后再纳入扣费范围。
|
||||
本次不做分档价格、不做会员免扣,也不迁移旧 `server-node` 逻辑。旧资产工坊角色主形象/动作生成与发布接口仍需要先补齐 Bearer 身份归属后再纳入扣费范围。旧资产生成流水 source 不再作为公开契约兼容。
|
||||
|
||||
@@ -16,6 +16,16 @@
|
||||
6. 启动测试运行态
|
||||
7. 后端推进摇杆输入、刷怪、吞噬收编、三合一、屏外清理和胜负裁决
|
||||
|
||||
### 1.1 2026-04-27 公开游玩次数补充
|
||||
|
||||
正式发布的大鱼吃小鱼作品需要记录公开游玩次数,落地口径如下:
|
||||
|
||||
1. `big_fish_creation_session.play_count` 保存该作品被正式启动的次数,默认值为 `0`。
|
||||
2. 只有平台作品详情、作品架等正式入口启动已发布作品时递增;创作结果页内的测试运行不计入。
|
||||
3. 前端作品摘要 contract 暴露 `playCount`,作品架展示与拼图一致使用该后端值。
|
||||
4. 本轮仅记录“进入玩法”次数,不记录大鱼吃小鱼总时长;个人 profile 的 RPG 时长统计仍由 runtime snapshot 负责。
|
||||
5. schema 变更需要同步 `migration.rs` 已纳入的 `big_fish_creation_session` 导入导出结构。
|
||||
|
||||
## 2. 本轮明确不做
|
||||
|
||||
1. 不在本文件内展开正式图片模型链、OSS 真相链和占位兼容层的细节;相关正式出图方案以 `BIG_FISH_FORMAL_IMAGE_GENERATION_2026-04-23.md` 为准。
|
||||
|
||||
@@ -17,6 +17,19 @@
|
||||
4. 入口必须在移动端单手可点,不遮挡舞台主体。
|
||||
5. 规则内容只做说明,不参与任何前端裁决;真实规则仍以后端运行快照为准。
|
||||
|
||||
## 游玩统计规则
|
||||
|
||||
所有作品都需要对自身以及用户做游玩统计。
|
||||
|
||||
大鱼吃小鱼正式运行时必须遵守:
|
||||
|
||||
1. 正式开始游玩已发布作品时,更新作品自身播放统计。
|
||||
2. 已登录用户写入 `profile_played_world`,`world_key = big-fish:{session_id}`。
|
||||
3. `profile_id` 保存大鱼作品号/会话号,`world_type = BIG_FISH`。
|
||||
4. `world_title` 使用玩法草稿标题,`world_subtitle` 优先使用副标题,其次使用核心乐趣。
|
||||
5. `owner_user_id` 使用大鱼作品归属用户 ID。
|
||||
6. 退出或结算上报 `elapsedMs` 后,后端按增量刷新 `profile_dashboard_state.total_play_time_ms` 和明细中的 `last_observed_play_time_ms`。
|
||||
|
||||
## 落地范围
|
||||
|
||||
1. `src/components/big-fish-runtime/BigFishRuntimeShell.tsx`
|
||||
|
||||
@@ -169,6 +169,24 @@ Node 侧入口位于:
|
||||
|
||||
## 4. 本轮边界决议
|
||||
|
||||
### 4.0 统一游玩统计规则
|
||||
|
||||
所有作品都需要对自身以及用户做游玩统计。
|
||||
|
||||
正式游玩开始时,玩法自己的作品真相表必须先更新自身统计;已登录用户还必须同步 upsert `profile_played_world` 明细。用户侧明细不是单纯计数,必须保留可跳转的稳定作品标识:
|
||||
|
||||
1. `world_key`
|
||||
2. `world_type`
|
||||
3. `profile_id`
|
||||
4. `world_title`
|
||||
5. `world_subtitle`
|
||||
6. `owner_user_id`
|
||||
7. `first_played_at`
|
||||
8. `last_played_at`
|
||||
9. `last_observed_play_time_ms`
|
||||
|
||||
当玩法有可观测时长时,后端按增量刷新 `profile_dashboard_state.total_play_time_ms`,并同步推进对应 `profile_played_world.last_observed_play_time_ms`。
|
||||
|
||||
### 4.1 先做 projection 读链
|
||||
|
||||
本轮 profile 三接口只做:
|
||||
@@ -377,6 +395,17 @@ Node 侧入口位于:
|
||||
|
||||
这些都等 `runtime_snapshot / save archive` 主链文档冻结后继续推进。
|
||||
|
||||
## 10.1 2026-04-27 统计写链修正
|
||||
|
||||
`runtime_snapshot / save archive` 主链已接入后,profile projection 的写入语义补充冻结如下:
|
||||
|
||||
1. 正式 RPG 游玩只通过 `PUT /api/runtime/save/snapshot` 刷新 `profile_dashboard_state` 与 `profile_played_world`。
|
||||
2. `runtimeMode = "preview"`、`runtimeMode = "test"` 或 `runtimePersistenceDisabled = true` 的快照不刷新 profile projection。
|
||||
3. 前端发起自动保存与手动保存前,必须先把 `runtimeStats.lastPlayTickAt` 到当前时间的 live 时长同步进 `runtimeStats.playTimeMs`,避免 15 秒内进入又退出时保存 0。
|
||||
4. `profile_played_world` 的一行表示“当前用户玩过这个世界”,不是全站作品热度计数;`playedWorldCount` 读取当前用户的去重世界数。
|
||||
5. `profile_dashboard_state.total_play_time_ms` 通过同一用户同一世界的 `runtimeStats.playTimeMs - last_observed_play_time_ms` 增量累积,后端使用 `saturating_sub` 防止旧快照回退导致负增量。
|
||||
6. 作品卡上的公开热度计数如果需要覆盖 RPG 作品,应另立公开作品统计方案;不能把个人 `profile_played_world` 误当成全站作品 `playCount`。
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
### 11.1 必跑
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 密码登录入口历史落地设计
|
||||
|
||||
> 2026-04-25 更新:当前产品策略已调整为“不开放密码注册”。新用户必须通过手机号验证码注册/登录,密码登录只面向已经登录后设置过密码的手机号账号。`POST /api/auth/entry` 只接受 `phone + password`,不支持邮箱、用户名或叙世号登录,也不承担自动建号能力。本文原有“密码自动建号”内容仅作为历史背景保留,当前落地以本更新和 [PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md](./PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md) 为准。
|
||||
>
|
||||
> 2026-04-28 更新:为开发期本地/测试服联调新增服务端环境变量 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED`,默认 `false`。仅当该变量显式为 `true` 时,`POST /api/auth/entry` 可对未知手机号用本次密码直接创建账号并登录;默认关闭时仍严格保持未知手机号返回 `401` 的生产语义。该开关不得用于生产环境,也不新增任何前端规则说明文案。
|
||||
|
||||
日期:`2026-04-21`
|
||||
|
||||
@@ -166,6 +168,13 @@
|
||||
2. 不创建账号。
|
||||
3. 不写 `password_hash`。
|
||||
|
||||
开发期例外:
|
||||
|
||||
1. 当 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true` 时,未知手机号会创建手机号账号。
|
||||
2. 新账号立即写入本次密码的 `password_hash`,并将 `password_login_enabled` 置为 `true`。
|
||||
3. 成功响应沿用密码登录响应体,`created` 只保留在领域结果中,不额外暴露到当前 HTTP contract。
|
||||
4. 手机号格式和密码长度校验仍完全沿用正式密码入口规则。
|
||||
|
||||
### 8.2 未设置密码
|
||||
|
||||
当账号存在但 `password_login_enabled = false` 时:
|
||||
@@ -233,6 +242,8 @@
|
||||
4. 邮箱、用户名或叙世号作为密码登录标识返回 `400`。
|
||||
5. 登录成功时返回 access token。
|
||||
6. 登录成功时写回 refresh cookie。
|
||||
7. `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED` 默认关闭时行为不变。
|
||||
8. 开关开启时,未知手机号可通过 `/api/auth/entry` 创建账号并登录;同手机号后续用相同密码登录复用同一用户,错误密码仍返回 `401`。
|
||||
|
||||
## 13. 完成定义
|
||||
|
||||
|
||||
131
docs/technical/PROFILE_REDEEM_CODE_IMPLEMENTATION_2026-04-28.md
Normal file
131
docs/technical/PROFILE_REDEEM_CODE_IMPLEMENTATION_2026-04-28.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 资料兑换码模块落地设计
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本轮在现有“我的”资料与钱包 projection 上新增兑换码能力。用户兑换成功后直接增加叙世币余额,写入 `profile_wallet_ledger`,并同步刷新 `profile_dashboard_state.wallet_balance`。
|
||||
|
||||
管理侧本轮只提供后端 API,不新增管理后台页面。私有兑换码创建时支持内部 `userId` 与公开叙世号两类输入,后端创建阶段统一解析成内部 `userId` 存储。
|
||||
|
||||
## 2. 兑换码类型
|
||||
|
||||
`RuntimeProfileRedeemCodeMode` 固定为三种:
|
||||
|
||||
| 类型 | 规则 |
|
||||
| --- | --- |
|
||||
| `Public` | 任意用户可兑换,`max_uses` 按用户独立计算。 |
|
||||
| `Unique` | 任意用户可兑换,`max_uses` 全局共用。 |
|
||||
| `Private` | 仅 `allowed_user_ids` 中的用户可兑换,`max_uses` 全局共用。 |
|
||||
|
||||
兑换码入库前必须 `trim + uppercase`。空兑换码、奖励为 0、次数为 0 均拒绝。
|
||||
|
||||
## 3. 表结构
|
||||
|
||||
### 3.1 `profile_redeem_code`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `code` | `String` | 主键,标准化后的兑换码。 |
|
||||
| `mode` | `RuntimeProfileRedeemCodeMode` | 兑换码模式。 |
|
||||
| `reward_points` | `u64` | 单次到账叙世币。 |
|
||||
| `max_uses` | `u32` | 公共码为单用户上限,唯一码/私有码为全局上限。 |
|
||||
| `global_used_count` | `u32` | 全局已使用次数。公共码也记录总使用次数,但不参与公共码上限判断。 |
|
||||
| `enabled` | `bool` | 是否启用。 |
|
||||
| `allowed_user_ids` | `Vec<String>` | 私有码允许用户;公共/唯一码存空数组。 |
|
||||
| `created_by` | `String` | 管理员用户 ID。 |
|
||||
| `created_at` | `Timestamp` | 创建时间。 |
|
||||
| `updated_at` | `Timestamp` | 更新时间。 |
|
||||
|
||||
### 3.2 `profile_redeem_code_usage`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `usage_id` | `String` | 主键,格式 `redeem:{code}:{user_id}:{micros}:{sequence}`。 |
|
||||
| `code` | `String` | 兑换码。 |
|
||||
| `user_id` | `String` | 兑换用户。 |
|
||||
| `amount_granted` | `u64` | 到账叙世币。 |
|
||||
| `created_at` | `Timestamp` | 兑换时间。 |
|
||||
|
||||
索引:`code`、`user_id`、`(code, user_id)`。
|
||||
|
||||
## 4. SpacetimeDB 过程
|
||||
|
||||
### 4.1 用户兑换
|
||||
|
||||
`redeem_profile_reward_code(input: RuntimeProfileRewardCodeRedeemInput) -> RuntimeProfileRewardCodeRedeemProcedureResult`
|
||||
|
||||
流程:
|
||||
|
||||
1. 标准化 code。
|
||||
2. 校验兑换码存在、启用、奖励大于 0。
|
||||
3. 按模式校验使用范围与次数。
|
||||
4. 同一事务内写入 `profile_redeem_code_usage`、增加钱包余额、写入 `profile_wallet_ledger`,最后更新 `profile_redeem_code.global_used_count`。
|
||||
5. 返回 `walletBalance`、`amountGranted` 与本次 `ledgerEntry`。
|
||||
|
||||
### 4.2 管理创建/更新
|
||||
|
||||
`admin_upsert_profile_redeem_code(input: RuntimeProfileRedeemCodeAdminUpsertInput) -> RuntimeProfileRedeemCodeAdminProcedureResult`
|
||||
|
||||
私有码必须至少解析出一个内部用户 ID。公共码与唯一码忽略 allowed 列表并存空数组。
|
||||
|
||||
### 4.3 管理停用
|
||||
|
||||
`admin_disable_profile_redeem_code(input: RuntimeProfileRedeemCodeAdminDisableInput) -> RuntimeProfileRedeemCodeAdminProcedureResult`
|
||||
|
||||
只更新 `enabled=false` 与 `updated_at`,不存在时返回“兑换码不存在”。
|
||||
|
||||
## 5. Axum API
|
||||
|
||||
用户接口:
|
||||
|
||||
- `POST /api/profile/redeem-codes/redeem`
|
||||
- `POST /api/runtime/profile/redeem-codes/redeem`
|
||||
|
||||
请求:`{ "code": "WELCOME2026" }`
|
||||
|
||||
成功返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"walletBalance": 130,
|
||||
"amountGranted": 100,
|
||||
"ledgerEntry": {
|
||||
"id": "redeem:WELCOME2026:user:1777392000000000:0",
|
||||
"amountDelta": 100,
|
||||
"balanceAfter": 130,
|
||||
"sourceType": "redeem_code_reward",
|
||||
"createdAt": "2026-04-28T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
管理员接口:
|
||||
|
||||
- `POST /admin/api/profile/redeem-codes`
|
||||
- `POST /admin/api/profile/redeem-codes/disable`
|
||||
|
||||
管理员接口复用现有 `require_admin_auth`。
|
||||
|
||||
## 6. 错误文案
|
||||
|
||||
| 场景 | message |
|
||||
| --- | --- |
|
||||
| 空 code | `兑换码不能为空` |
|
||||
| 不存在 | `兑换码不存在` |
|
||||
| 停用 | `兑换码已停用` |
|
||||
| 奖励为 0 | `兑换码奖励无效` |
|
||||
| 次数耗尽 | `兑换次数已用完` |
|
||||
| 私有码账号不匹配 | `该兑换码不适用于当前账号` |
|
||||
| 私有码无允许用户 | `私有兑换码必须指定可兑换用户` |
|
||||
|
||||
## 7. 前端交互
|
||||
|
||||
“我的”页头像右侧入口由 `会员充值` 改为 `兑换码`。点击打开独立模态窗口,窗口内只保留输入框、兑换按钮和后端返回提示,不展示兑换规则说明。
|
||||
|
||||
成功后展示 `已到账 X 叙世币`,并刷新 profile dashboard。失败后直接展示后端 `message`。
|
||||
|
||||
## 8. 测试矩阵
|
||||
|
||||
- Rust/module-runtime:覆盖公共码、唯一码、私有码、失败场景、流水来源和余额累加。
|
||||
- Axum:覆盖用户鉴权、管理员鉴权、runtime error 到 400 的映射和兼容路径。
|
||||
- 前端:覆盖入口替换、独立 modal、成功刷新余额和失败展示后端 message。
|
||||
- 验证命令:`cargo test`、目标前端测试、`npm run api-server:maincloud`、`npm run check:encoding`。
|
||||
@@ -39,6 +39,15 @@
|
||||
|
||||
新增拼图成绩表,按“关卡作品 + 网格规格 + 用户”维护最佳成绩。
|
||||
|
||||
正式开始拼图关卡时还必须同步用户玩过作品明细:
|
||||
|
||||
1. 作品自身统计继续更新 `puzzle_work_profile.play_count`。
|
||||
2. 已登录用户写入 `profile_played_world`,`world_key = puzzle:{profile_id}`。
|
||||
3. `profile_id` 保存拼图作品号,`world_type = PUZZLE`。
|
||||
4. `world_title` 使用关卡名,`world_subtitle` 使用作品摘要,`owner_user_id` 使用拼图作者用户 ID。
|
||||
5. 下一关切换到新 `profile_id` 时按同一规则再次写入。
|
||||
6. 排行榜提交携带的 `elapsedMs` 是本关可观测时长,后端按增量累计到 `profile_dashboard_state.total_play_time_ms`。
|
||||
|
||||
建议字段:
|
||||
|
||||
1. `entry_id`
|
||||
|
||||
@@ -23,7 +23,7 @@ spacetime sql <db> "SELECT * FROM custom_world_gallery_entry"
|
||||
| 领域 | 表 |
|
||||
| --- | --- |
|
||||
| 认证 | `auth_store_snapshot`, `user_account`, `auth_identity`, `refresh_session` |
|
||||
| 运行时档案 | `runtime_setting`, `runtime_snapshot`, `user_browse_history`, `profile_dashboard_state`, `profile_wallet_ledger`, `profile_played_world`, `profile_save_archive` |
|
||||
| 运行时档案 | `runtime_setting`, `runtime_snapshot`, `user_browse_history`, `profile_dashboard_state`, `profile_wallet_ledger`, `profile_redeem_code`, `profile_redeem_code_usage`, `profile_played_world`, `profile_save_archive` |
|
||||
| RPG 运行时 | `story_session`, `story_event`, `npc_state`, `inventory_slot`, `battle_state`, `treasure_record`, `quest_record`, `quest_log`, `player_progression`, `chapter_progression` |
|
||||
| 世界创作 | `custom_world_profile`, `custom_world_session`, `custom_world_agent_session`, `custom_world_agent_message`, `custom_world_agent_operation`, `custom_world_draft_card`, `custom_world_gallery_entry` |
|
||||
| 拼图 | `puzzle_agent_session`, `puzzle_agent_message`, `puzzle_work_profile`, `puzzle_runtime_run` |
|
||||
@@ -133,6 +133,27 @@ SELECT * FROM profile_wallet_ledger WHERE user_id = '<user_id>';
|
||||
SELECT * FROM profile_wallet_ledger WHERE user_id = '<user_id>' ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### `profile_redeem_code`
|
||||
|
||||
- 作用:运营发放的叙世币兑换码,支持公共码、唯一码和私有码。
|
||||
- 结构:`code PK: String`, `mode: RuntimeProfileRedeemCodeMode`, `reward_points: u64`, `max_uses: u32`, `global_used_count: u32`, `enabled: bool`, `allowed_user_ids: Vec<String>`, `created_by: String`, `created_at: Timestamp`, `updated_at: Timestamp`。
|
||||
- 索引:主键 `code`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM profile_redeem_code WHERE code = '<CODE>';
|
||||
```
|
||||
|
||||
### `profile_redeem_code_usage`
|
||||
|
||||
- 作用:记录每一次兑换行为,为公共码用户维度计次、唯一/私有码全局计次提供依据。
|
||||
- 结构:`usage_id PK: String`, `code: String`, `user_id: String`, `amount_granted: u64`, `created_at: Timestamp`。
|
||||
- 索引:`code`, `user_id`, `(code, user_id)`。
|
||||
|
||||
```sql
|
||||
SELECT * FROM profile_redeem_code_usage WHERE code = '<CODE>';
|
||||
SELECT * FROM profile_redeem_code_usage WHERE user_id = '<user_id>';
|
||||
```
|
||||
|
||||
### `profile_played_world`
|
||||
|
||||
- 作用:记录用户玩过的世界及最后游玩时间,用于个人页历史和继续游戏入口。
|
||||
|
||||
Reference in New Issue
Block a user