零散小工具合集:bcrypt 密码哈希、mail 邮件发送、sms 短信发送、ip 客户端 IP 识别、useragent UA 解析、strutil 字符串工具、 i18n 国际化。各自职责单一,无横向依赖。
关键导出
bcrypt.Hashpw(password) (string, error)/HashpwWithCost(password, cost int)/Verify(plaintext, hashed) error/Checkpw(plaintext, hashed) boolbcrypt.DefaultCost=10 / MaxPasswordSize=72;错误ErrPasswordMismatch / ErrPasswordTooLongmail.Mailer / New(cfg) *Mailer / Init() / Enabled() bool / Send(ctx, to, subject, body) error;错误ErrDisabledsms.Sender interface{ Send(ctx, phone, params map[string]string) error }sms.Init() / Enabled() bool / SendCode(ctx, phone, code) error / SetSender(s) (restore func());错误ErrDisabledip.ClientIP(r *http.Request) stringuseragent.Parse(ua string) (browser, os string)strutil.Capitalize(s string) string/IsHTTP(link string) booli18n.Locale / LocaleZhCN / LocaleEnUS / DefaultLocale=LocaleZhCNi18n.Parse(tag string) (Locale, bool)/NewContext(ctx, loc)/FromContext(ctx) Localei18n.Msg(ctx, code string, args ...any) string/MsgLocale(loc, code, args...) string
典型用法
go
// internal/auth/service/password_auth_strategy.go:53
return bcrypt.Checkpw(loginBody.Password, user.Password)
// internal/resource/handler/captcha_handler.go:55
if err := sms.SendCode(ctx, phoneNumber, code); err != nil { /* ... */ }
// mail
mail.Send(ctx, to, "登录验证码", code)
// internal/auth/service/sys_login_service.go:50,56
loginUser.IPAddr = ip.ClientIP(req)
browser, osName := useragent.Parse(req.Header.Get("User-Agent"))
// internal/auth/handler/auth_handler.go:75
c.JSON(http.StatusOK, response.OkMsg(i18n.Msg(c.Request.Context(), "user.logout.success")))坑/约定
WARNING
- bcrypt Go 标准库把盐折进哈希串一次性返回,没有独立产盐 API(不像 hutool 的
gensalt);密码超 72 字节返回ErrPasswordTooLong;Verify区分「密码不匹配」与「哈希格式非法」两类错误。 - mail 只做纯文本单收件人(唯一场景是登录验证码,引第三方库不划算);SSL(465)连上来就握手,STARTTLS(587)先明文再升级,两种加密方式不能混(对着 465 发 STARTTLS 会卡住);主题按 RFC 2047 编码(中文主题直接塞会乱码);正文
\n统一成\r\n;ctx 只用于取消建连(SMTP 会话本身不可中途取消)。 - sms 模板变量名固定
code(阿里云模板${code});未开启时defaultSender=nil,SendCode返回ErrDisabled;换/加厂商只需再写一个Sender,调用方不动;SetSender仅供测试注入。 - ip 所有代理头都是可伪造的,依赖 nginx 覆写;
X-Forwarded-For是逗号分隔链路,取第一段才是最初客户端;剥掉 IPv6 字面量的方括号;RemoteAddr带端口且 IPv6 形如[::1]:8080。 - useragent 底层用
github.com/mssola/useragent;只返回名字不含版本(登录日志只记名字)。 - strutil
Capitalize按 rune 处理(UTF-8 安全);IsHTTP有意不用strings.HasPrefix的模糊匹配,非 http 协议进来只会拼出脏路径。 - i18n 词条缺失时返回 code 本身(不报错)——故键拼错不会有编译错误,
enum.LoginType用TestLoginTypeKeysExistInCatalog把键与词条表钉在一起;回落链:精确语言 → 语言级(zh→zh-cn)→DefaultLocale;Parse列表形态取第一段,下划线归一为连字符,只裁空格/制表符(挡\r\n),长度上限 32;语言由middleware.I18n写进gin.Context与request.Context()。 :::
相关页:/modules/auth、/modules/resource、/pkg-reference/middleware。