前后端分离下实现验证码服务的逻辑分析

人机交互模式


基于 Gin 实现一套验证码

具体来说,CAPTCHA库可以:

  • 生成各种形式的CAPTCHA图像,例如文本、数字和数学表达式等。
  • 添加噪声和扭曲效果以增加CAPTCHA的难度和安全性。
  • 提供快速而可靠的CAPTCHA验证功能,以确保只有真正的人类用户能够通过验证。

该库还支持自定义CAPTCHA参数,例如图像大小、字体、颜色、背景等。

实现工具类

// Captcha 方便后期扩展
type Captcha struct {}

// 单例
var captchaInstance *Captcha
func Instance() *Captcha {
 if captchaInstance==nil {
  captchaInstance = &Captcha{}
 }
 return captchaInstance
}

声明了一个结构体,方便后期在 captcha 这个库上进行扩展

// CreateImage 创建图片验证码
func (this *Captcha) CreateImage() string {
 length := captcha.DefaultLen
 captchaId := captcha.NewLen(length)
 return captchaId
}

创建验证码也很容易,我们这里直接全部使用他默认的配置,生产6位数的数字验证码,后期有需要可以参考 captcha 库进行调整配置。

这里会返回一个 ID 给我们,这个 ID 就是刚我画的流程图里面的 key,他关联了一个随机数,也就是图片的数字。

重载

因为不可能用户每次都能输对,所以有些时候用户不能识别的情况下就需要进行重新生成随机数,也就是重新生成一张图片,但是 key 也就是 ID 是不能变的,此时就要用到重载。

// Reload 重载
func (this *Captcha) Reload(captchaId string) bool {
 return captcha.Reload(captchaId)
}

验证

// Verify 验证
func (this *Captcha) Verify(captchaId,val string) bool {
 return captcha.VerifyString(captchaId, val)
}

传输

把图片输出给用户,captcha 库他会生成一个图片的二进制流,你只需要把这个二进制流返回回去即可得到图片。

// GetImageByte 获取图片二进制流
func (this *Captcha) GetImageByte(captchaId string) []byte {
 var content bytes.Buffer
 err := captcha.WriteImage(&content, captchaId, captcha.StdWidth, captcha.StdHeight)
 if err!=nil {
  log.Println(err)
  return nil
 }
 return content.Bytes()
}

总结

// Captcha 方便后期扩展
type Captcha struct {}


var captchaInstance *Captcha
func Instance() *Captcha {
	if captchaInstance==nil {
		captchaInstance = &Captcha{}
	}
	return captchaInstance
}

// CreateImage 创建图片验证码
func (this *Captcha) CreateImage() string {
	length := captcha.DefaultLen
	captchaId := captcha.NewLen(length)
	return captchaId
}

// Reload 重载
func (this *Captcha) Reload(captchaId string) bool {
	return captcha.Reload(captchaId)
}

// Verify 验证
func (this *Captcha) Verify(captchaId,val string) bool {
	return captcha.VerifyString(captchaId, val)
}

// GetImageByte 获取图片二进制流
func (this *Captcha) GetImageByte(captchaId string) []byte {
	var content bytes.Buffer
	err := captcha.WriteImage(&content, captchaId, captcha.StdWidth, captcha.StdHeight)
	if err!=nil {
		log.Println(err)
		return nil
	}
	return content.Bytes()
}

测试

func main() {
	r := gin.New()
	// 创建
	// 这里方便看到效果 我用的 GET 请求,实际生产最好不要用 GET
	r.Handle("GET", "/captcha/create", func(c *gin.Context) {
		imgId := captcha.Instance().CreateImage()
		c.JSON(http.StatusOK,
			gin.H{
				"code": 200,
				"key": imgId,
				"url": "/captcha/img/"+imgId,
			})
	})
	// 现实图片
	r.Handle("GET", "/captcha/img/:key", func(c *gin.Context) {
		captchaId := c.Param("key")
		c.Writer.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
		c.Writer.Header().Set("Pragma", "no-cache")
		c.Writer.Header().Set("Expires", "0")
		c.Writer.Header().Set("Content-Type", "image/png")
		// 重载一次
		captcha.Instance().Reload(captchaId)
		// 输出图片
		c.Writer.Write(captcha.Instance().GetImageByte(captchaId))
	})
	// 校验
	r.Handle("GET", "/captcha/verify/:key/:val", func(c *gin.Context) {
		captchaId := c.Param("key")
		val := c.Param("val")
		if captcha.Instance().Verify(captchaId,val) {
			c.JSON(http.StatusOK, gin.H{"code": 200})
		}else{
			c.JSON(http.StatusOK, gin.H{"code": 400})
		}
	})

	r.Run(":9000")
}