Redis验证码实践 | 青训营

289 阅读3分钟

使用Redis制作一个简化的验证码系统。在这个验证码系统中,Redis起到了一个核心的暂存和验证的角色。以下是详细描述该业务流程中Redis的使用逻辑:

  1. 验证码生成:当需要为用户生成一个验证码时,首先会使用随机算法产生一个六位数的验证码。

  2. 验证码存储:生成验证码后,为了使其在后续能够被验证,需要将验证码存储在某处。这就是Redis的首次关键使用场景。使用Redis而不是其他存储方法的好处是,Redis提供了超高的数据读写速度。这确保了验证码的读取和写入几乎是实时的,为用户提供了流畅的体验。当验证码被存储时,它与一个特定的用户ID关联,并设置了一个5分钟的过期时间。这意味着,经过5分钟,Redis会自动删除该验证码,这对于验证码这样的短暂性数据非常有用。

  3. 验证码验证:当用户输入一个验证码以完成验证时,系统需要查询之前存储的验证码并与用户输入进行比较。这是Redis的第二个关键使用场景。由于Redis的高性能特性,可以确保在毫秒级时间内检索验证码,从而为用户提供即时的验证反馈。系统会根据用户ID从Redis中获取与之关联的验证码,并与用户输入进行对比。如果匹配,则验证成功;否则,验证失败。

  4. 自动过期:如上所述,Redis中存储的验证码有一个5分钟的生命周期。这是通过设置Redis键的过期时间来实现的。这确保了验证码的短暂性,同时也避免了数据存储的堆积,因为过期的验证码对系统来说已经没有用处了。

Model (models/verification.go)

package models

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

	"github.com/go-redis/redis/v8"
	"golang.org/x/net/context"
)

var ctx = context.Background()

type VerificationModel struct {
	rdb *redis.Client
}

func NewVerificationModel() *VerificationModel {
	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
		DB:   0,
	})
	return &VerificationModel{rdb: rdb}
}

// GenerateCode 生成一个六位随机验证码
func (vm *VerificationModel) GenerateCode() string {
	return fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))
}

// StoreCode 将验证码存储到Redis中
func (vm *VerificationModel) StoreCode(userID string, code string) error {
	return vm.rdb.Set(ctx, userID, code, 5*time.Minute).Err()
}

// VerifyCode 验证Redis中存储的验证码是否与用户输入匹配
func (vm *VerificationModel) VerifyCode(userID string, inputCode string) bool {
	storedCode, err := vm.rdb.Get(ctx, userID).Result()
	if err != nil {
		return false
	}
	return inputCode == storedCode
}

Controller (controllers/verification_controller.go)

package controllers

import (
	"net/http"

	"your_project/models"
	"github.com/gin-gonic/gin"
)

type VerificationController struct {
	model *models.VerificationModel
}

func NewVerificationController(model *models.VerificationModel) *VerificationController {
	return &VerificationController{model: model}
}

// GenerateAndStoreCode 为特定用户生成并存储验证码
func (vc *VerificationController) GenerateAndStoreCode(c *gin.Context) {
	userID := c.Param("userID")
	code := vc.model.GenerateCode()
	err := vc.model.StoreCode(userID, code)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "无法存储验证码"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "验证码已生成并存储"})
}

// VerifyUserInput 检查用户输入的验证码是否正确
func (vc *VerificationController) VerifyUserInput(c *gin.Context) {
	userID := c.Param("userID")
	inputCode := c.Query("code")

	isValid := vc.model.VerifyCode(userID, inputCode)
	if isValid {
		c.JSON(http.StatusOK, gin.H{"message": "验证码正确!"})
	} else {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "验证码错误!"})
	}
}

Main (main.go)

package main

import (
	"your_project/controllers"
	"your_project/models"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	model := models.NewVerificationModel()
	controller := controllers.NewVerificationController(model)

	// Endpoints
	r.GET("/generate/:userID", controller.GenerateAndStoreCode)
	r.GET("/verify/:userID", controller.VerifyUserInput)

	r.Run() // 默认监听8080端口
}