Files
wangjia 23dff69c62 feat(backend): 授权改为时长兑换券体系 + 退役 ed25519/HMAC + 平台生码工具
- 新增 license_codes 码池表 + model.LicenseCode;licenses 加 tier 档位列
- LicenseService.Redeem:单事务 FOR UPDATE 校验码未用 → 时长叠加(可叠加,0=永久)
  → 写 type/tier/max_devices → 绑设备(超限整笔回滚) → 标记已用 → 即时失效 phase 缓存
  路由仍 POST /license/activate,客户端零破坏
- util.GenerateRedeemCode/NormalizeCode:JIUKU-XXXX-XXXX 短码(crypto/rand)
- cmd/gencode:平台批量生成兑换码并落库;删除 cmd/issue、cmd/genkey
- 退役 ed25519 + HMAC:删 util/license_key、GenerateKey、License 全部 config 字段
  及生产启动私钥校验;trial 改直接建行(无需私钥、去 Fatal)
- tier 档位钩子默认 standard,分档消费模式后续设计
- 测试:Redeem 全场景(叠加/过期重置/永久/一码一次/无效/设备上限回滚)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:14:27 +08:00

41 lines
1.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package util
import (
"crypto/rand"
"strings"
)
// codeAlphabet 兑换码字母表:剔除易混字符 0/O/1/I/L,避免人工抄录歧义。
const codeAlphabet = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"
// codePrefix 兑换码固定前缀,便于一眼识别归属。
const codePrefix = "JIUKU"
// GenerateRedeemCode 生成一个随机兑换码,展示格式 JIUKU-XXXX-XXXX8 位随机段)。
// 使用 crypto/rand + 无歧义字母表;归一化(NormalizeCode)后入库与比对。
func GenerateRedeemCode() string {
const n = 8
buf := make([]byte, n)
if _, err := rand.Read(buf); err != nil {
// crypto/rand 失败属系统级异常,调用方(CLI 生码)应直接失败而非产出弱码。
panic("crypto/rand failed: " + err.Error())
}
out := make([]byte, n)
for i, b := range buf {
out[i] = codeAlphabet[int(b)%len(codeAlphabet)]
}
return codePrefix + "-" + string(out[:4]) + "-" + string(out[4:])
}
// NormalizeCode 归一化兑换码:转大写、去掉连字符/空格,得到入库与查表用的规范形式。
func NormalizeCode(code string) string {
var b strings.Builder
for _, r := range strings.ToUpper(code) {
if r == '-' || r == ' ' || r == '\t' || r == '\n' || r == '\r' {
continue
}
b.WriteRune(r)
}
return b.String()
}