Revel中调用短信API发验证码需封装SmsService,从conf读取密钥等配置,用带超时的http.Client发POST请求;验证码必须存Redis,key为sms:code:{phone}并设300秒TTL;校验前须trim空格和不可见字符,比对用==;防并发重复消费须用GETDEL原子操作。

Revel 框架里怎么调用短信服务商 API 发验证码
Revel 本身不内置短信能力,必须对接第三方服务(如阿里云 SMS、腾讯云 SMS、Mob 短信等)。关键不是 Revel 有没有“发送验证码”函数,而是你如何在 AppController 或独立 service 中安全、可测地发起 HTTP 请求。
常见错误是把 API 密钥硬编码在 controller 里,或直接用 http.Post 拼 URL —— 这会导致密钥泄露、无法 mock 测试、参数拼错 400。
- 把短信配置(
access_key_id、access_key_secret、sign_name、template_code)写进conf/app.conf,用revel.Config.String()读取 - 封装一个
SmsService.SendCode(phone string, code string) error,内部用net/http.Client发 POST,Body 用url.Values构建(阿里云要求 form 表单) - 务必设置超时:
http.Client{Timeout: 10 * time.Second},避免请求卡住整个 goroutine - 别忽略返回状态码和 JSON body 中的
Code字段(如阿里云返回"OK"才算成功,"isv.BUSINESS_LIMIT_CONTROL"是触发频率限制)
验证码存在哪?Redis 还是数据库?
必须用 Redis。原因很实际:验证码要带 TTL(比如 5 分钟过期),且高频写入+单次读取+自动清理——数据库做这事既慢又浪费连接。
Revel 没强制依赖 Redis,得自己集成。容易踩的坑是连接没复用、没设 context 超时、key 设计不区分环境。
- 初始化一个全局
*redis.Client(推荐用github.com/go-redis/redis/v8),在app.Init阶段建立连接 - key 格式建议:
sms:verify:{phone}:{ip}(防同一 IP 刷不同手机号),或更简单:sms:code:{phone} - 存值用
SET key code EX 300(300 秒),不要先SET再EXPIRE—— 非原子操作可能存成功但没设过期 - 校验时用
GETDEL(Redis 6.2+)或GET + DEL组合,避免“查到但别人抢先用了”的竞态
前端传来的验证码怎么跟 Redis 里的比?大小写和空格怎么办
用户输入的验证码常带空格、换行、全角字符,后端不清洗就直接比对会失败。这不是 Revel 的 bug,是输入处理疏忽。
别用 strings.EqualFold 简单忽略大小写 —— 验证码通常全是数字或数字+大写字母,大小写混淆反而降低安全性。
- 接收参数用
c.Params.Get("code")后,立刻执行:strings.TrimSpace(strings.ReplaceAll(code, " ", "")) - 如果服务商模板定义的是 6 位纯数字,就加校验:
len(code) == 6 && regexp.MustCompile(`^\d+$`).MatchString(code) - Redis 取出的值也要 trim,因为某些 client 存的时候可能带不可见字符(尤其用其他语言存的)
- 比对用
==即可,别上bytes.Equal—— 字符串比较足够快,且语义清晰
为什么 /verify 接口总返回 “验证码已失效”,但 Redis 里明明有值
最常见原因是时区或时间精度不一致:Revel 应用服务器时间 vs Redis 服务器时间 vs 用户手机系统时间。但更大概率是代码里重复消费了验证码。
典型错误模式:先 GET 判断是否存在 → 存在再 DEL → 最后比对。这中间有间隙,两个并发请求可能都走到“存在”分支,导致第二个请求比对失败却误报“已失效”。
- 必须用原子操作:Redis 的
GETDEL(推荐)或 Lua 脚本EVAL "local v=redis.call('GET',KEYS[1]); redis.call('DEL',KEYS[1]); return v" 1 key - 如果用
GET + DEL,确保在同一个 pipeline 里发,且用WATCH监控 key(复杂,不推荐) - 日志里记下 Redis 返回的原始值(
fmt.Printf("redis got: [%s]", val)),比对前后是否真一样 —— 曾有 case 是 Redis 存了"123456\n",而用户输的是"123456"
真正难调试的是跨机房 Redis 延迟,或者 Revel 启动多个实例却共用一套 Redis 连接池配置,导致连接被意外关闭。这些不会报错,只会让验证码行为飘忽。


















