在现代应用开发中,手机验证码验证功能已成为用户身份认证和安全验证的常见手段,尤其是在登录注册、密码重置等场景中。本文将重点讲解如何使用 Golang 实现一个简单、高效的手机验证码功能,并探讨其在后端的存储和验证流程。

在此方案中,我们将结合常用工具(如 Redis 和第三方短信服务)提供完整的实现示例。


一、功能需求分析

功能需求:

  1. 用户通过手机号请求发送验证码。

  2. 后端生成随机验证码并通过第三方短信服务发送至用户手机。

  3. 后端存储验证码及其有效期(建议使用缓存工具,如 Redis)。

  4. 前端用户输入验证码进行提交,后端验证用户输入的验证码是否正确与有效。

安全性需求:

  1. 验证码应尽量短期有效(如 5 分钟)。

  2. 用户连续输入验证码错误达到一定次数时,限制进一步验证请求。

  3. 验证成功后,验证码应即刻失效,避免重复使用。


二、技术选型

  1. 语言:Golang

  1. 短信发送

  • 使用阿里云、Twilio 等成熟的第三方短信服务完成短信发送功能。

  1. 验证码存储

  • 使用 Redis 高性能缓存解决方案存储验证码,便于快速读写和设置自动过期时间。


三、实现验证码功能

下面我们通过代码实现从生成验证码到后端验证的完整流程。

1. 构建项目目录结构

项目目录大致如下:

/go-otp-auth
  ├── main.go              # 主入口
  ├── redis.go             # Redis 初始化与操作
  ├── otp.go               # 验证码生成与验证逻辑
  ├── sms.go               # 第三方短信服务集成
  ├── go.mod               # Go 模块配置

2. 编写 Redis 工具模块 (redis.go)

Redis 用于存储验证码及其过期时间,确保高性能的读写操作。

package main

import (
    "context"
    "log"
    "time"

    "github.com/go-redis/redis/v8"
)

var ctx = context.Background()

// 初始化 Redis 客户端
func InitRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379", // Redis 地址
        Password: "",               // Redis 密码(默认为空)
        DB:       0,                // 默认 DB
    })

    _, err := client.Ping(ctx).Result()
    if err != nil {
        log.Fatalf("Failed to connect Redis: %v", err)
    }

    return client
}

// 全局 Redis 客户端
var RedisClient = InitRedis()

// 将数据存储到 Redis
func StoreToRedis(key string, value string, expiration time.Duration) error {
    err := RedisClient.Set(ctx, key, value, expiration).Err()
    if err != nil {
        return err
    }
    return nil
}

// 从 Redis 获取数据
func GetFromRedis(key string) (string, error) {
    val, err := RedisClient.Get(ctx, key).Result()
    if err != nil {
        return "", err
    }
    return val, nil
}

// 删除 Redis 数据
func DeleteFromRedis(key string) error {
    err := RedisClient.Del(ctx, key).Err()
    if err != nil {
        return err
    }
    return nil
}

3. 编写验证码模块 (otp.go)

此模块负责生成随机验证码,并封装验证码的存储及验证逻辑。

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// 初始化随机数种子
func init() {
    rand.Seed(time.Now().UnixNano())
}

// 生成 6 位随机验证码
func GenerateRandomCode(length int) string {
    code := ""
    for i := 0; i < length; i++ {
        code += fmt.Sprintf("%d", rand.Intn(10)) // 生成 0-9 的随机数
    }
    return code
}

// 保存验证码到 Redis(过期时间为 5 分钟)
func SaveOTPToRedis(phone string, code string) error {
    expiration := 5 * time.Minute
    return StoreToRedis(phone, code, expiration)
}

// 验证用户输入的验证码
func ValidateOTP(phone string, inputCode string) (bool, error) {
    // 从 Redis 获取存储的验证码
    storedCode, err := GetFromRedis(phone)
    if err != nil {
        return false, fmt.Errorf("verification code expired or not existed")
    }

    // 比较验证码是否一致
    if inputCode == storedCode {
        // 验证成功后删除 Redis 中的验证码
        DeleteFromRedis(phone)
        return true, nil
    }

    return false, fmt.Errorf("verification code is incorrect")
}

4. 编写短信服务模块 (sms.go)

这里以阿里云短信服务为例,调用其 API 实现验证码短信发送。实际开发中需要填写具体的 API 配置。

package main

import (
    "fmt"
)

// 模拟短信发送(伪代码,替换为实际短信 API 调用)
func SendSMS(phone string, code string) error {
    // 这里实现短信发送逻辑 (如通过阿里云短信服务,Twilio API 等)
    message := fmt.Sprintf("Your verification code is: %s", code)
    fmt.Printf("Sending SMS to %s: %s\n", phone, message)

    // 假设发送成功:
    return nil
}

5. 实现主入口 (main.go)

通过 HTTP 接口模拟用户操作请求验证码和验证验证码的处理逻辑。

package main

import (
    "fmt"
    "net/http"
)

// 发送验证码接口
func SendOTPHandler(w http.ResponseWriter, r *http.Request) {
    phone := r.URL.Query().Get("phone") // 获取手机号
    if phone == "" {
        http.Error(w, "Phone number is required", http.StatusBadRequest)
        return
    }

    // 生成验证码
    code := GenerateRandomCode(6)

    // 发送验证码短信
    err := SendSMS(phone, code)
    if err != nil {
        http.Error(w, "Failed to send SMS", http.StatusInternalServerError)
        return
    }

    // 保存验证码到 Redis
    err = SaveOTPToRedis(phone, code)
    if err != nil {
        http.Error(w, "Failed to store OTP", http.StatusInternalServerError)
        return
    }

    fmt.Fprintf(w, "OTP sent successfully to %s", phone)
}

// 验证验证码接口
func ValidateOTPHandler(w http.ResponseWriter, r *http.Request) {
    phone := r.URL.Query().Get("phone")
    code := r.URL.Query().Get("code")
    if phone == "" || code == "" {
        http.Error(w, "Phone number and code are required", http.StatusBadRequest)
        return
    }

    // 验证验证码
    valid, err := ValidateOTP(phone, code)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    if valid {
        fmt.Fprintf(w, "Verification succeeded!")
    } else {
        http.Error(w, "Invalid verification code", http.StatusUnauthorized)
    }
}

func main() {
    http.HandleFunc("/send-otp", SendOTPHandler)
    http.HandleFunc("/validate-otp", ValidateOTPHandler)

    fmt.Println("Server is running on :8080...")
    http.ListenAndServe(":8080", nil)
}

四、测试与验证

  1. 启动服务后,用浏览器或 Postman 测试以下接口:

  • http://localhost:8080/send-otp?phone=1234567890:发送验证码。

  • http://localhost:8080/validate-otp?phone=1234567890&code=123456:验证验证码。

  1. 确保 Redis 中存储的验证码按预期工作,并在过期或验证后清除。


五、总结

本文通过一个完整的示例展示了如何使用 Golang 实现手机验证码的功能。关键点包括:

  • 安全性:验证码短期内有效,且验证成功后删除。

  • 高性能:利用 Redis 作为临时存储,提升效率。

  • 扩展性:短信服务通过模块化设计,可以轻松切换到其他服务。