package userstore import ( "context" "crypto/rand" "fmt" "strings" ) const passwordAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789" const usernameAlphabet = "abcdefghijkmnopqrstuvwxyz23456789" const usernameFirst = "abcdefghijkmnopqrstuvwxyz" // RandomPassword 生成可读随机密码(不含易混字符 0/O/1/l)。 func RandomPassword(n int) (string, error) { if n < 8 { n = 12 } b := make([]byte, n) if _, err := rand.Read(b); err != nil { return "", err } out := make([]byte, n) for i := range b { out[i] = passwordAlphabet[int(b[i])%len(passwordAlphabet)] } return string(out), nil } // RandomUsername 生成随机登录名(字母开头,仅小写字母与数字,默认 10 位)。 func RandomUsername() (string, error) { const n = 10 b := make([]byte, n) if _, err := rand.Read(b); err != nil { return "", err } out := make([]byte, n) out[0] = usernameFirst[int(b[0])%len(usernameFirst)] for i := 1; i < n; i++ { out[i] = usernameAlphabet[int(b[i])%len(usernameAlphabet)] } return string(out), nil } // AllocUniqueUsername 分配全局唯一随机用户名。 func AllocUniqueUsername(ctx context.Context, exists func(context.Context, string) (bool, error)) (string, error) { var last error for i := 0; i < 16; i++ { u, err := RandomUsername() if err != nil { return "", err } ok, err := exists(ctx, u) if err != nil { return "", err } if ok { last = fmt.Errorf("username collision") continue } return u, nil } if last != nil { return "", fmt.Errorf("无法生成唯一用户名: %v", last) } return "", fmt.Errorf("无法生成唯一用户名") } // Deprecated: 保留测试兼容;新逻辑请用 RandomUsername / AllocUniqueUsername。 func SuggestAdminUsername(slug string) string { s := strings.ToLower(strings.TrimSpace(slug)) var b strings.Builder for _, r := range s { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { b.WriteRune(r) } } base := b.String() if base == "" { base = "co" } if len(base) > 20 { base = base[:20] } return base + "_adm" } // Deprecated: 见 AllocUniqueUsername。 func NextUsernameCandidate(base string, attempt int) string { base = strings.TrimSpace(base) if base == "" { base = "user" } if attempt <= 0 { return base } suffix, err := RandomPassword(4) if err != nil { return fmt.Sprintf("%s%d", base, attempt) } return fmt.Sprintf("%s_%s", base, strings.ToLower(suffix)) }