go 的 regexp 因缺乏正向先行断言且默认贪婪匹配,无法原生表达“多条件并存”(如数字、大写字母、特殊字符同时存在),单正则易漏判;需用多个 matchstring 配合 strings.trimspace 预处理。

为什么 regexp 在 Go 里检测密码强度容易漏判?
Go 的 regexp 默认是贪婪匹配,且不支持“必须同时满足多个条件”的原生断言(比如“至少一个数字 AND 至少一个大写字母”)。直接写 regexp.MustCompile(<code>[A-Z].*[0-9]) 看似合理,但会漏掉数字在前、大写字母在后的组合;更糟的是,它无法表达“至少一个数字、一个字母、一个特殊字符,且长度 ≥8”这种并列约束。
- 用
^.*$包裹整个正则没用,它只是匹配整行,不解决逻辑组合问题 -
regexp.FindAllString不能告诉你“缺哪一类”,只返回匹配到的子串 - 多个
MatchString调用是可行解法,但要注意:空字符串或全空白符时,strings.TrimSpace必须前置,否则" "会被误判为“含字母”
示例片段:
func checkPassword(s string) bool {
s = strings.TrimSpace(s)
if len(s) < 8 {
return false
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(s)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(s)
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(s)
hasSpecial := regexp.MustCompile(`[^a-zA-Z0-9\s]`).MatchString(s)
return hasUpper && hasLower && hasDigit && hasSpecial
}用 unicode 包比正则更稳的场景
当你要区分“中文字符”“Emoji”“全角标点”或“控制字符”时,regexp 很难写对(比如 \p{Han} 在 Go 的 regexp 中不支持)。此时直接遍历 rune,用 unicode.IsLetter、unicode.IsDigit、unicode.IsPunct 等判断更可靠,也更容易调试。
-
unicode.IsLetter(r)覆盖中日韩英等所有字母,不含数字和下划线 -
unicode.IsPunct(r)匹配标点,但不包括空格、制表符、换行符(那些归unicode.IsSpace管) - 遇到 Emoji(如 ?)时,
unicode.IsSymbol(r)才能捕获,IsPunct会返回 false - 不要对每个 rune 都调用
unicode.Is*四次——先缓存结果,或用位掩码一次收集多类特征
规则引擎不是必须写 DSL:从 map[string]func(string) bool 开始
初学 Go 规则引擎,别一上来就搞 AST 解析或 YACC。密码强度本质是一组独立校验函数的合取(AND),用 map 存规则名和函数,再统一执行即可:
立即学习“go语言免费学习笔记(深入)”;
type Rule struct {
Name string
Check func(string) bool
Msg string
}
<p>var passwordRules = []Rule{
{"minLength", func(s string) bool { return len(strings.TrimSpace(s)) >= 8 }, "too short"},
{"hasUpper", func(s string) bool { return regexp.MustCompile(<code>[A-Z]</code>).MatchString(s) }, "no uppercase"},
{"hasLower", func(s string) bool { return regexp.MustCompile(<code>[a-z]</code>).MatchString(s) }, "no lowercase"},
{"hasDigit", func(s string) bool { return regexp.MustCompile(<code>[0-9]</code>).MatchString(s) }, "no digit"},
}- 规则顺序无关,但错误提示顺序建议按用户感知重要性排(长度 → 字符类型)
-
Check函数内部别做耗时操作(如 HTTP 请求、文件读取),规则引擎必须低延迟 - 如果后续要支持“警告级规则”(如“连续重复字符 >3”),可加
Level int字段区分 error/warn
测试时最容易忽略的边界:空格、BOM、零宽字符
真实用户粘贴密码时,可能带不可见字符:\uFEFF(BOM)、\u200B(零宽空格)、\u00A0(不间断空格)。这些字符会让 len(s) 和 len(strings.TrimSpace(s)) 不一致,导致“明明输够 8 位却提示太短”。
- 测试用例必须显式包含:
"abc123AB\u200B"、"\uFEFFpassword123"、"pass word123"(中间有全角空格) - 建议在入口统一清理:
strings.Map(func(r rune) rune { if unicode.IsControl(r) || unicode.IsMark(r) { return -1 }; return r }, s) -
unicode.IsMark(r)能干掉组合字符(如变音符号),避免 “café” 被误判为含非法字符
规则越贴近真实输入,越容易暴露清理逻辑的缺口。别只测干净 ASCII 字符串。










