validatepassword 必须返回 error 而非 bool,以便区分不同错误类型并支持前端精准定位;密码校验需先做长度检查,再聚合所有错误(如缺数字、特殊字符等)统一返回。

别用 MD5 或 SHA256 + salt 存密码,也别把强度校验塞进哈希流程里——这两件事必须拆开做,且各自有不可妥协的实现细节。
ValidatePassword 必须返回 error 而不是 bool
返回 bool 会让上层无法区分“密码太短”还是“缺特殊字符”,更没法在注册表单里精准标红对应字段。真实业务中,邮箱、密码、确认密码常批量校验,错误需聚合上报。
- 用
fmt.Errorf("password: missing digit"),字段名加冒号前缀,前端可直接提取定位 - 避免
errors.New("missing digit")—— 后续加 trace ID 或结构化日志会很麻烦 - 若需收集全部问题(非短路),内部用
var errs []error累积,最后errors.Join(errs)(Go 1.20+) - 长度检查永远放最前:
if len(pwd) 就直接 return,别等遍历完 rune 再判
逐 rune 遍历判断字符类型,别用正则或 strings.ContainsAny
Go 的 regexp 不支持正向先行断言,单条正则写不出“必须同时含大写、小写、数字、特殊字符”的逻辑;strings.ContainsAny(s, "0123456789") 只认 ASCII 数字,对全角“123”或阿拉伯数字无效。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 用
unicode.IsUpper(r)、unicode.IsLower(r)、unicode.IsDigit(r)、unicode.IsPunct(r) || unicode.IsSymbol(r)分类统计 - 特殊字符范围要显式定义:若业务禁止
#和|,就!strings.ContainsRune("!@$_", r)排除,别依赖IsPunct(它包含 \u200b 这类控制符) - 前置清理不可见字符:
strings.TrimSpace不够,得用strings.Map(func(r rune) rune { if unicode.IsControl(r) || unicode.IsSpace(r) && !unicode.IsPrint(r) { return -1 }; return r }, pwd)
bcrypt.GenerateFromPassword 是唯一推荐的哈希入口
bcrypt 自动管理随机 salt、可调 cost、编码 salt+hash 到同一字符串,你不需要手拼、解析或存两个字段。任何手动实现的 salt + MD5/SM3/SHA256 都是倒退。
- 安装:
go get -u golang.org/x/crypto/bcrypt - 生成哈希:
hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)——DefaultCost当前是 10,足够平衡安全与性能 - 验证哈希:
err := bcrypt.CompareHashAndPassword(hashed, []byte(input)),只用于登录比对,绝不用于强度校验 - 注意:
bcrypt静默截断超 72 字节输入,所以ValidatePassword中的长度检查必须用len([]rune(pwd))判语义长度,而非len([]byte(pwd))(emoji 占多个字节)
真实用户粘贴的密码含不可见字符,本地测试根本碰不到
零宽空格(\u200b)、BOM 头、换行符混在密码里是常态。这些字符会让 len([]byte(pwd)) 和 len([]rune(pwd)) 差很多,导致强度校验通过但哈希结果异常,甚至登录失败。
- 所有密码输入必须先过
strings.Map清理控制符,再做长度和字符分类 - 日志里打印哈希前的
fmt.Printf("raw pwd bytes: %v", []byte(pwd)),能快速暴露隐藏字符 - 数据库字段类型设为
VARCHAR(100)足够存 bcrypt 哈希(通常 60 字符左右),但别设成TEXT——没意义还影响索引效率
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










