🎯 为什么需要“专门”的邀请码?
你可能见过这些场景:
- 用户注册时填一个
AB3XK9邀请好友; - 活动页面输入
Zm2QpL兑换奖励; - 分享链接带
?code=8NwR4e自动绑定关系。
这些 6 位字母+数字 的短码,比 UUID(36 位)友好太多——但怎么生成才靠谱?
❌ 常见错误姿势
| 错误做法 | 问题 |
|---|---|
用 math/rand 生成 | 可预测,安全性差 |
| 用自增 ID 转 62 进制 | 容易被遍历(1,2,3…) |
| 直接截取 UUID | 太长、含 -、不美观 |
| 用时间戳哈希 | 碰撞率高,分布不均 |
我们需要的是:短 + 随机 + 安全 + 低冲突。
🔐 核心原则:安全随机 + 合理字符集
✅ 字符集选择
我们选用 62 个字符:
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
- 排除
0/O、1/l/I?不推荐!
虽然能防“视觉混淆”,但会降低熵(从 62 → 56),反而增加碰撞概率。
更好的做法是:前端展示时用等宽字体 + 大小写区分。
✅ 随机源:必须用 crypto/rand
import "crypto/rand"
math/rand 是伪随机,种子固定就可预测;而 crypto/rand 基于操作系统熵池,密码学安全,适合验证码、邀请码、Token 等场景。
🧪 实战:生成 6 位邀请码
// invite.go
package main
import (
"crypto/rand"
"fmt"
"math/big"
)
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func GenerateInviteCode(length int) (string, error) {
b := make([]byte, length)
charsetSize := big.NewInt(int64(len(charset)))
for i := range b {
index, err := rand.Int(rand.Reader, charsetSize)
if err != nil {
return "", err
}
b[i] = charset[index.Int64()]
}
return string(b), nil
}
func main() {
code, _ := GenerateInviteCode(6)
fmt.Println("你的邀请码:", code)
}
输出示例:
你的邀请码: aB3xK9
你的邀请码: Zm2QpL
你的邀请码: 8NwR4e
💡 小知识:
rand.Int(rand.Reader, max)返回[0, max)的安全随机整数,完美适配字符集索引。
📊 碰撞概率:真的够用吗?
6 位 × 62 字符 = 62⁶ ≈ 568 亿种组合。
根据 生日悖论,当生成 100 万个 邀请码时,碰撞概率仅约 0.000088%(不到万分之一)!
| 生成数量 | 碰撞概率 |
|---|---|
| 10,000 | ~0.00000009% |
| 100,000 | ~0.000009% |
| 1,000,000 | ~0.00088% |
| 10,000,000 | ~0.088% |
✅ 对于绝大多数应用(用户量 < 百万级),纯随机 6 位码完全够用!
🛠️ 进阶:如何保证“绝对唯一”?
如果业务要求 100% 不重复(比如金融场景),可以这样做:
方案一:生成后查库(推荐)
func CreateUniqueInviteCode(db *sql.DB) (string, error) {
const maxRetries = 3
for i := 0; i < maxRetries; i++ {
code, err := GenerateInviteCode(6)
if err != nil {
return "", err
}
// 检查是否已存在
var exists bool
err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM invites WHERE code = ?)", code).Scan(&exists)
if err != nil {
return "", err
}
if !exists {
return code, nil // 成功!
}
}
return "", fmt.Errorf("failed to generate unique code after %d retries", maxRetries)
}
⚠️ 注意:不要无限重试!设上限(如 3~5 次),失败后可降级为 7 位或报错。
方案二:预生成 + 批量分配(高并发场景)
- 启动时预生成 10 万个邀请码存入数据库;
- 用时直接
UPDATE ... LIMIT 1取一个; - 由后台任务补充库存。
这种方式避免实时生成+查重的锁竞争,适合秒杀、裂变等高并发场景。
🧼 代码封装建议
把生成逻辑放入独立包,便于测试和复用:
// internal/invite/code.go
package invite
import (
"crypto/rand"
"math/big"
)
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func Generate(length int) (string, error) {
// ...同上
}
然后在业务层调用:
code, err := invite.Generate(6)
if err != nil {
// handle error
}
✅ 总结:邀请码生成 Checklist
- 使用
crypto/rand保证随机性 - 字符集包含 62 个字符(A-Za-z0-9)
- 长度 6 位,平衡可读性与空间
- 生成后查库确保唯一(按需)
- 封装成独立函数,避免散落在业务代码中