因为Iris本身不提供短信能力,必须对接阿里云等第三方服务;Controller只负责调度,需抽离SmsService接口、依赖注入、外置密钥配置、手机号正则校验、Redis存验证码并设TTL、用hash.Equal防时序攻击、显式设HTTP超时。

短信验证码发送为什么不能直接用 Iris 自带的 context.WriteJSON 就完事?
因为 Iris 本身不提供短信能力,所有发送逻辑必须对接第三方服务(如阿里云、腾讯云、容联云),而 MVC 中的 Controller 层只负责调度——它得知道该调哪个 Service、传什么参数、怎么处理失败。常见错误是把 HTTP 请求、密钥硬编码在 Controller 里,导致无法单元测试、密钥泄露、难以切换服务商。
实操建议:
- 把短信发送抽成独立
SmsService接口,用依赖注入注册到Application.Container,Controller 只调smsService.Send(code, phone) - 密钥、签名、模板 ID 等配置走
iris.Configuration().Other或外部 JSON 文件,**绝不在代码里写死** - 发送前必须校验手机号格式(
^1[3-9]\d{9}$),否则第三方接口会直接报错,比如阿里云返回"InvalidParameter.PhoneNumber" - 验证码建议用
rand.Intn(900000) + 100000生成 6 位纯数字,避免字母(部分通道不支持)
Session 存验证码还是用 Redis?Iris 默认 Session 有坑
Iris 的默认 sessiondb 基于内存或文件,多实例部署时会话不共享,用户请求打到不同服务器就校验失败。更严重的是:默认 session 过期时间单位是秒,但你设 Expires: 5 * time.Minute,实际可能被截断为整数秒,导致提前失效。
实操建议:
- 生产环境强制用
redis作为 session 后端:iris.Sessions().UseDatabase(redis.New(...)) - 验证码字段名别用
code这种通用名,改用verify_code_138xxxx1234拼接手机号,避免不同用户互相覆盖 - 存的时候加 TTL:
sess.SetWithTTL("verify_code_138xxxx1234", "827419", 5*time.Minute),比依赖 session 全局过期更可控 - 别在 session 里存明文手机号,至少做简单哈希(如
fmt.Sprintf("%x", md5.Sum([]byte(phone))))防日志泄露
校验接口为什么老返回 400 Bad Request?检查这三处
典型错误不是逻辑写错,而是 HTTP 协议层没对齐。Iris 的 ctx.PostValue 和 ctx.URLParam 混用、JSON 解析失败、Content-Type 不匹配,都会让校验直接卡在中间件外。
实操建议:
- 统一用 JSON 提交(
application/json),Controller 用ctx.ReadJSON(&req)解析,别用PostValue—— 前者能自动返回400并带字段错误,后者取不到就 panic - 结构体字段必须加
json:"phone"标签,且首字母大写,否则ReadJSON无法赋值 - 校验前先查 session:
codeFromSession := sess.GetString("verify_code_" + req.Phone),如果为空,直接返回ctx.StatusCode(400); ctx.WriteString("code expired or not sent") - 比对用
hash.Equal(golang.org/x/crypto/subtle)防时序攻击,别用==
为什么本地调试发得通,上线就超时?http.Client 超时必须显式设
第三方短信 API 大多有 3~5 秒响应上限,而 Go 默认 http.Client 的 Timeout 是 0(无限等待)。Iris 的 iris.HTTPClient 也是同理,没配就卡死协程。
实操建议:
- 初始化短信客户端时,必须指定超时:
client := &http.Client{Timeout: 8 * time.Second} - 用
context.WithTimeout包一层请求上下文,防止整个 handler 卡住:ctx, cancel := context.WithTimeout(context.Background(), 7*time.Second) - 阿里云 SDK 示例中
config.WithTimeout参数单位是毫秒,填5000别填5,否则超时立刻触发 - 日志里记下实际耗时:
start := time.Now(); defer func(){ log.Printf("sms api took %v", time.Since(start)) }()
真正麻烦的从来不是“怎么调通”,而是“怎么不让它在凌晨三点静默失败”。验证码流程里每个环节都要有可追踪的日志、明确的错误码、降级开关(比如失败时允许跳过校验——仅限内网测试环境),否则一个超时就会让整条注册链路雪崩。


















