以前写项目,校验都用的图形验证码,这次用go体验了一下行为验证码
接下来我们要用到的go库是

它支持多种校验方式
安装Go-Captcha
安装模块
go get -u github.com/wenlng/go-captcha/v2@latest
安装预制静态资源
go get -u github.com/wenlng/go-captcha/v2@latest
初始化验证码
我参考作者文档和demo编写的以下方法
package common
import (
"github.com/golang/freetype/truetype"
"github.com/wenlng/go-captcha-assets/bindata/chars"
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
"github.com/wenlng/go-captcha-assets/resources/images"
"github.com/wenlng/go-captcha-assets/resources/tiles"
"github.com/wenlng/go-captcha/v2/base/option"
"github.com/wenlng/go-captcha/v2/click"
"github.com/wenlng/go-captcha/v2/rotate"
"github.com/wenlng/go-captcha/v2/slide"
"log"
)
// ClickCaptcha 全局点选验证码对象
var ClickCaptcha *click.Captcha
// SlideCaptcha 全局滑动验证码对象
var SlideCaptcha *slide.Captcha
// RotateCaptcha 全局旋转验证码对象
var RotateCaptcha *rotate.Captcha
// 验证码类型
const (
CaptchaTypeClick string = "click"
CaptchaTypeSlide string = "slide"
CaptchaTypeRotate string = "rotate"
)
// InitClickCaptcha
// @Description 初始化点选验证码
// @Author Ham 2025-02-19 20:42:43
func InitClickCaptcha() {
builder := click.NewBuilder()
// fonts
fonts, err := fzshengsksjw.GetFont()
if err != nil {
log.Fatalln(err)
}
// background images
imgs, err := images.GetImages()
if err != nil {
log.Fatalln(err)
}
builder.SetResources(
click.WithChars(chars.GetChineseChars()),
click.WithFonts([]*truetype.Font{fonts}),
click.WithBackgrounds(imgs),
)
textCapt := builder.Make()
ClickCaptcha = &textCapt
}
// InitSlideCaptcha
// @Description 初始化滑动验证码
// @Author Ham 2025-02-19 20:45:06
func InitSlideCaptcha() {
builder := slide.NewBuilder(
//slide.WithGenGraphNumber(2),
slide.WithEnableGraphVerticalRandom(true),
)
// background images
imgs, err := images.GetImages()
if err != nil {
log.Fatalln(err)
}
graphs, err := tiles.GetTiles()
if err != nil {
log.Fatalln(err)
}
var newGraphs = make([]*slide.GraphImage, 0, len(graphs))
for i := 0; i < len(graphs); i++ {
graph := graphs[i]
newGraphs = append(newGraphs, &slide.GraphImage{
OverlayImage: graph.OverlayImage,
MaskImage: graph.MaskImage,
ShadowImage: graph.ShadowImage,
})
}
// set resources
builder.SetResources(
slide.WithGraphImages(newGraphs),
slide.WithBackgrounds(imgs),
)
slideCapt := builder.Make()
SlideCaptcha = &slideCapt
}
// InitRotateCaptcha
// @Description 初始化旋转验证码
// @Author Ham 2025-02-19 20:50:14
func InitRotateCaptcha() {
builder := rotate.NewBuilder(rotate.WithRangeAnglePos([]option.RangeVal{
{Min: 20, Max: 330},
}))
// background images
imgs, err := images.GetImages()
if err != nil {
log.Fatalln(err)
}
// set resources
builder.SetResources(
rotate.WithImages(imgs),
)
rotateCapt := builder.Make()
RotateCaptcha = &rotateCapt
}
// InitCaptcha
// @Description 初始化验证码
// @Author Ham 2025-02-19 20:50:27
func InitCaptcha() {
InitClickCaptcha()
InitSlideCaptcha()
InitRotateCaptcha()
}
之后在main方法里面,调用initCaptcha(),初始化验证码
验证码生成和校验工具
参考作者demo编写了以下代码,第一次看作者的demo可能有点一头雾水,没有注释,可以看看下面的
package util
import (
"backend/internal/common"
)
// 生成验证码时调用下面的GenerateCaptcha方法,传入验证码类型,然后将captchaData返回给前端,用于生成验证码
// GenerateCaptcha
// @Description 生成验证码
// @Author Ham 2025-02-19 21:00:43
// @Param captchaType 验证码类型
// @Return captchaData 验证码数据
func GenerateCaptcha(captchaType string) (captchaData map[string]interface{}, err error) {
switch captchaType {
// 点选验证码
case common.CaptchaTypeClick:
{
captcha := *common.ClickCaptcha
generatedCaptcha, err := captcha.Generate()
if err != nil {
return nil, err
}
thumbImageBase64, err := generatedCaptcha.GetThumbImage().ToBase64()
if err != nil {
return nil, err
}
masterImageBase64, err := generatedCaptcha.GetMasterImage().ToBase64()
if err != nil {
return nil, err
}
captchaData = map[string]interface{}{
// 这里生成唯一的主键,用于存入缓存
"uuid": "",
"thumbImage": thumbImageBase64,
"masterImage": masterImageBase64,
}
// 根据业务实际,将下面的数据用上面的uuid存入缓存,用于之后校验时对比
data := generatedCaptcha.GetData()
return captchaData, nil
}
// 滑动验证码
case common.CaptchaTypeSlide:
{
captcha := *common.SlideCaptcha
generatedCaptcha, err := captcha.Generate()
if err != nil {
return nil, err
}
tileImageBase64, err := generatedCaptcha.GetTileImage().ToBase64()
if err != nil {
return nil, err
}
masterImageBase64, err := generatedCaptcha.GetMasterImage().ToBase64()
if err != nil {
return nil, err
}
captchaData = map[string]interface{}{
// 这里生成唯一的主键,用于存入缓存
"uuid": "",
"tileImage": tileImageBase64,
"masterImage": masterImageBase64,
"thumbX": generatedCaptcha.GetData().TileX,
"thumbY": generatedCaptcha.GetData().TileY,
"thumbWidth": generatedCaptcha.GetData().Width,
"thumbHeight": generatedCaptcha.GetData().Height,
}
// 根据业务实际,将下面的数据用上面的uuid存入缓存,用于之后校验时对比
data := generatedCaptcha.GetData()
return captchaData, nil
}
// 旋转验证码
case common.CaptchaTypeRotate:
{
captcha := *common.RotateCaptcha
generatedCaptcha, err := captcha.Generate()
if err != nil {
return nil, err
}
thumbImageBase64, err := generatedCaptcha.GetThumbImage().ToBase64()
if err != nil {
return nil, err
}
masterImageBase64, err := generatedCaptcha.GetMasterImage().ToBase64()
if err != nil {
return nil, err
}
captchaData = map[string]interface{}{
// 这里生成唯一的主键,用于存入缓存
"uuid": "",
"thumbImage": thumbImageBase64,
"masterImage": masterImageBase64,
}
// 根据业务实际,将下面的数据用上面的uuid存入缓存,用于之后校验时对比
data := generatedCaptcha.GetData()
return captchaData, nil
}
default:
{
return
}
}
}
// 下面是校验的演示,这里是用了redis,业务中,请替换自己的缓存逻辑,校验时调用ValidateCaptcha即可
// ValidateClickCaptcha
// @Description 校验点选验证码
// @Author Ham 2025-02-20 13:27:28
// @Param captchaData 验证码数据
// @Return validated 校验结果
// @Return err 错误
func ValidateClickCaptcha(captchaData map[string]interface{}) (validated bool, err error) {
// 根据业务实际,从缓存中取出正确校验数据
uuidString := captchaData["uuid"].(string)
if uuidString == "" {
return false, common.NewError("无效的验证码")
}
ctx := context.TODO()
key := fmt.Sprintf(redisKey.RedisKeyCaptcha, uuidString)
result, err := cache.RedisClient.Get(ctx, key).Result()
if err != nil {
err = common.NewError("无效的验证码")
return
}
if result == "" {
return false, common.NewError("无效的验证码")
}
// 从缓存结果解析正确校验数据
rightCaptchaData := map[int]*click.Dot{}
err = json.Unmarshal([]byte(result), &rightCaptchaData)
if err != nil {
return false, common.NewError("解析验证码失败")
}
// 校验验证码
dotsInterface := captchaData["dots"].([]interface{})
dots := make([]float64, len(dotsInterface))
for i, v := range dotsInterface {
dots[i], _ = v.(float64) // 将每个元素转换为float64
}
// 点数*2 为x,y坐标总数,数量应与请求传来的x,y坐标数相同
if (len(rightCaptchaData) * 2) == len(dots) {
for i := 0; i < len(rightCaptchaData); i++ {
dot := rightCaptchaData[i]
j := i * 2
k := i*2 + 1
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", dots[j]), 64)
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", dots[k]), 64)
validated = click.CheckPoint(int64(sx), int64(sy), int64(dot.X), int64(dot.Y), int64(dot.Width), int64(dot.Height), 0)
if !validated {
break
}
}
}
// 如果校验成功,根据业务实际删除验证码在缓存中的缓存
if validated {
cache.RedisClient.Del(ctx, key)
}
return
}
// ValidateSlideCaptcha
// @Description 校验滑动验证码
// @Author Ham 2025-02-20 13:27:28
// @Param captchaData 验证码数据
// @Return validated 校验结果
// @Return err 错误
func ValidateSlideCaptcha(captchaData map[string]interface{}) (validated bool, err error) {
// 根据业务实际,从缓存中取出正确校验数据
uuidString := captchaData["uuid"].(string)
if uuidString == "" {
return false, common.NewError("无效的验证码")
}
ctx := context.TODO()
key := fmt.Sprintf(redisKey.RedisKeyCaptcha, uuidString)
result, err := cache.RedisClient.Get(ctx, key).Result()
if err != nil {
err = common.NewError("无效的验证码")
return
}
if result == "" {
return false, common.NewError("无效的验证码")
}
// 从缓存结果解析正确校验数据
var rightCaptchaData *slide.Block
err = json.Unmarshal([]byte(result), &rightCaptchaData)
if err != nil {
return false, common.NewError("解析验证码失败")
}
// 校验验证码
pointsInterface := captchaData["points"].([]interface{})
points := make([]float64, len(pointsInterface))
for i, v := range pointsInterface {
points[i], _ = v.(float64) // 将每个元素转换为float64
}
if 2 == len(points) {
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", points[0]), 64)
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", points[1]), 64)
validated = slide.CheckPoint(int64(sx), int64(sy), int64(rightCaptchaData.X), int64(rightCaptchaData.Y), 4)
}
// 如果校验成功,根据业务实际删除验证码在缓存中的缓存
if validated {
cache.RedisClient.Del(ctx, key)
}
return
}
// ValidateRotateCaptcha
// @Description 校验旋转验证码
// @Author Ham 2025-02-20 14:14:07
// @Param captchaData 验证码数据
// @Return validated 校验结果
// @Return err 错误
func ValidateRotateCaptcha(captchaData map[string]interface{}) (validated bool, err error) {
// 根据业务实际,从缓存中取出正确校验数据
uuidString := captchaData["uuid"].(string)
if uuidString == "" {
return false, common.NewError("无效的验证码")
}
ctx := context.TODO()
key := fmt.Sprintf(redisKey.RedisKeyCaptcha, uuidString)
result, err := cache.RedisClient.Get(ctx, key).Result()
if err != nil {
err = common.NewError("无效的验证码")
return
}
if result == "" {
return false, common.NewError("无效的验证码")
}
// 从缓存结果解析正确校验数据
var rightCaptchaData *rotate.Block
err = json.Unmarshal([]byte(result), &rightCaptchaData)
if err != nil {
return false, common.NewError("解析验证码失败")
}
// 校验验证码
angle := captchaData["angle"].(float64)
sAngle, _ := strconv.ParseFloat(fmt.Sprintf("%v", angle), 64)
validated = rotate.CheckAngle(int64(sAngle), int64(rightCaptchaData.Angle), 2)
// 如果校验成功,根据业务实际删除验证码在缓存中的缓存
if validated {
cache.RedisClient.Del(ctx, key)
}
return
}
// ValidateCaptcha
// @Description 校验验证码
// @Author Ham 2025-02-20 13:27:28
// @Param captchaType 验证码类型
// @Param captchaData 验证码数据
// @Return validated 校验结果
// @Return err 错误
func ValidateCaptcha(captchaType string, captchaData map[string]interface{}) (validated bool, err error) {
switch captchaType {
case CaptchaTypeClick:
return ValidateClickCaptcha(captchaData)
case CaptchaTypeSlide:
return ValidateSlideCaptcha(captchaData)
case CaptchaTypeRotate:
return ValidateRotateCaptcha(captchaData)
default:
return false, common.NewError("不存在的验证码类型")
}
}
以上三种校验方法的形参captchaData分别为
// 点选验证码:
{
uuid (string),
dots ([]int)
}
// 滑动验证码
{
uuid (string),
points ([]int)
}
// 旋转验证码
{
uuid (string),
angle (int)
}
校验时前端像上面这样传数据来校验就可以了
uuid在前端生成验证码时请求后端生成验证码接口获取,
dots,points,angle的计算方法参考下面每一个demo的confirmEvent方法
go-captcha-example/web/vue/src/hooks at master · wenlng/go-captcha-example
正文完