docs: license-design 增补 v2 时长兑换券模型说明
顶部新增 v2 节,标注 v1 的 Ed25519 token 方案已废弃,记录兑换券 模型/数据表/Redeem 流程/与 v1 差异;运行时降级 phase 两版共用。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,45 @@
|
||||
# 授权信息功能 — 完整方案设计
|
||||
|
||||
> 本文档覆盖「付费授权」功能的产品决策、数据模型、API 设计、安全方案、前端方案及分期实施计划。所有编码工作在本文档评审通过后按计划分批推进。
|
||||
> ⚠️ **本文档第 1–11 节为 v1(Ed25519 自包含签名 token 方案),已被 v2「时长兑换券」模型取代。**
|
||||
> 实际线上实现以下方「## v2」为准;下方 v1 各节仅作历史保留,其中「激活时验签」「license_token 列」「AdminOnly 激活」等已不再适用。
|
||||
|
||||
---
|
||||
|
||||
## v2 实际实现(时长兑换券模型,2026-06 起)
|
||||
|
||||
### 为什么改
|
||||
|
||||
v1 把许可证做成「一个 Ed25519 签名 token = 一张完整授权」,但运行时从不验签(签名形同虚设),且 token 内 `expires_at` 是**绝对日期**、不可叠加,不符合「买一张码续 1 年、可买多张累加」的消费直觉。经产品方确认改为**时长兑换券**。
|
||||
|
||||
### 模型要点
|
||||
|
||||
- **时长券**:每张码代表一段**时长**(`duration_days`),兑换 = `新到期 = max(今天, 当前到期) + 时长`,**可叠加**;`duration_days=0` 表示永久(到期置 NULL)。
|
||||
- **短码 + 服务端码表**:码形如 `JIUKU-7F3A-9K2D`(`util.GenerateRedeemCode`,crypto/rand + 无歧义字母表),归一化(`util.NormalizeCode`)后存 `license_codes` 表。**一码一次**由服务端码表 `status` 强制(签名无法防重放,故 v2 退役 ed25519)。
|
||||
- **档位钩子**:`license_codes.tier` / `licenses.tier`(默认 `standard`),为将来 pro/max-like 分档预留;当前不据此做能力差异,分档消费模式后续设计。
|
||||
- **签发归平台方**:码由 CLI `go run ./cmd/gencode -type annual -days 365 -count N` 批量生成落库,用户购买/活动获得后在 App「设置 → 授权」兑换。**不开放 App 内自助签发**(防店铺管理员自签终身授权)。
|
||||
|
||||
### 数据模型
|
||||
|
||||
- 新表 `license_codes`(码池):`code`(uniq) / `type` / `tier` / `duration_days` / `max_devices` / `status`(unused/redeemed/void) / `redeemed_shop_id` / `redeemed_at` / `redeemed_device_id` / `batch` / `note`。见 `model.LicenseCode` + `schema.sql` + `testutil`。
|
||||
- `licenses` 表新增 `tier` 列(additive)。`license_key` 列降级为「当前权益占位」:trial 用 `TRIAL-<uuid>`、无既有行兜底用 `REDEEM-<uuid>`,兑换扩展时不改原值。**v1 的 `license_token` 列未实现,已废弃。**
|
||||
|
||||
### 兑换流程(`LicenseService.Redeem`,路由仍 `POST /license/activate` 以兼容客户端)
|
||||
|
||||
单事务 + `FOR UPDATE`(check-then-act 并发安全):① 锁码校验 unused(否则 `无效激活码/已被使用/已失效`)→ ② 锁本店最新 `is_active` 授权行(无则新建)→ ③ 叠加到期、写 `type/tier/max_devices=max(现值,码值)` → ④ 绑定本设备(幂等 + `max_devices` 上限,超限整笔回滚)→ ⑤ 标记码 `redeemed` → ⑥ 提交后 `InvalidateLicensePhase` 即时生效。
|
||||
|
||||
### 与 v1 的差异(务必注意)
|
||||
|
||||
| 维度 | v1(已废弃) | v2(现状) |
|
||||
|------|------|------|
|
||||
| 码形态 | Ed25519 246 字符 token | 短码 `JIUKU-XXXX-XXXX` + 服务端码表 |
|
||||
| 到期语义 | token 内绝对日期 | 时长券叠加,DB `expires_at` 为准 |
|
||||
| 验签 | 计划激活时验签(实际从未生效) | 退役 ed25519,改服务端码表查验 |
|
||||
| 签发 | `cmd/issue` 打印 token(需手动 INSERT) | `cmd/gencode` 批量生成并落库 |
|
||||
| 激活权限 | 计划加 `AdminOnly` | 维持 `ReadOnly` 豁免、不加 AdminOnly(兑换是店主自助行为) |
|
||||
| trial | Ed25519 签发 | 直接建行(无需私钥),`createTrialLicense` 去除 Fatal |
|
||||
| 运行时降级 | 同 v2(`LicenseGuard` 读 DB `expires_at` 实时算 phase,30s 缓存) | **不变,沿用** |
|
||||
|
||||
> v1 第 7 节「授权状态机与拦截层」(`CalcLicensePhase` / `LicenseGuard` / grace/readonly/locked)**已落地且 v2 沿用**,是两版共同的运行时基础。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user