Go 生成6位唯一邀请码

41 阅读3分钟

🎯 为什么需要“专门”的邀请码?

你可能见过这些场景:

  • 用户注册时填一个 AB3XK9 邀请好友;
  • 活动页面输入 Zm2QpL 兑换奖励;
  • 分享链接带 ?code=8NwR4e 自动绑定关系。

这些 6 位字母+数字 的短码,比 UUID(36 位)友好太多——但怎么生成才靠谱?

❌ 常见错误姿势

错误做法问题
math/rand 生成可预测,安全性差
用自增 ID 转 62 进制容易被遍历(1,2,3…)
直接截取 UUID太长、含 -、不美观
用时间戳哈希碰撞率高,分布不均

我们需要的是:短 + 随机 + 安全 + 低冲突


🔐 核心原则:安全随机 + 合理字符集

✅ 字符集选择

我们选用 62 个字符:

const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  • 排除 0/O1/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 位,平衡可读性与空间
  • 生成后查库确保唯一(按需)
  • 封装成独立函数,避免散落在业务代码中