package userstore import ( "fmt" "regexp" "strings" "unicode" ) var tenantSlugRe = regexp.MustCompile(`^[a-z][a-z0-9-]{1,31}$`) // 路径段保留字:不可用作公司 slug(与后续 /{slug}/ 路由冲突) var reservedTenantSlugs = map[string]struct{}{ "api": {}, "ai": {}, "gateway": {}, "platform": {}, "_platform": {}, "admin": {}, "assets": {}, "static": {}, "www": {}, "m": {}, "public": {}, "health": {}, "ops": {}, "console": {}, "login": {}, "register": {}, "favicon.ico": {}, "robots.txt": {}, } // NormalizeTenantSlug 规范化公司路径 slug(全局唯一,用于 www.yuxinda.com/{slug}/)。 func NormalizeTenantSlug(raw string) (string, error) { s := strings.TrimSpace(strings.ToLower(raw)) s = strings.ReplaceAll(s, "_", "-") s = strings.ReplaceAll(s, " ", "-") var b strings.Builder prevDash := false for _, r := range s { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { b.WriteRune(r) prevDash = false continue } if r == '-' { if b.Len() == 0 || prevDash { continue } b.WriteByte('-') prevDash = true continue } // 跳过其它字符(含中文);中文名需用户另填 slug } s = strings.Trim(b.String(), "-") if s == "" { return "", fmt.Errorf("公司路径 slug 不能为空(请用英文/数字,如 aaa)") } if !tenantSlugRe.MatchString(s) { return "", fmt.Errorf("slug 须为 2–32 位,小写字母开头,仅含 a-z / 0-9 / -") } if _, bad := reservedTenantSlugs[s]; bad { return "", fmt.Errorf("slug %q 为系统保留字", s) } return s, nil } // SuggestTenantSlug 从公司名生成候选(中文名可能得不到可用 slug,需人工填写)。 func SuggestTenantSlug(name string) string { name = strings.TrimSpace(name) if name == "" { return "" } // 纯 ASCII 名:直接规范化 ascii := true for _, r := range name { if r > unicode.MaxASCII { ascii = false break } } if ascii { s, err := NormalizeTenantSlug(name) if err == nil { return s } } return "" }