go实现验证码

技术颜良 / 2023-08-19 / 原文

Go进阶37:重构我的base64Captcha图形验证码项目

Go进阶37:重构我的base64Captcha图形验证码项目

Go Report CardGoDocBuild Statuscodecovstability-stableFoundation

🎃Base64captcha🎃 几行代码就可以定义自己内容的图形验证码库,支持任意unicode字符的内容.

1. 📒 文档&Demo 📒

  • English
  • 中文文档
  • Playground

2. 🚀 快速上手 🚀

2.1 📥 下载base64Captcha包 📥

go get -u github.com/mojocn/base64Captcha

2.2 🏂 在您的项目中使用base64Captcha 🏂

2.2.1 🏇 实现Store interface 或者使用自带memory store 🏇

  • Build-in Memory Store(只支持单机部署,多台服务器请自定义redis store)
type Store interface {
	// Set sets the digits for the captcha id.
	Set(id string, value string)

	// Get returns stored digits for the captcha id. Clear indicates
	// whether the captcha must be deleted from the store.
	Get(id string, clear bool) string
	
    //Verify captcha's answer directly
	Verify(id, answer string, clear bool) bool
}

2.2.2 🏄 实现Driver interface 或者使用自带 drivers 🏄

包自带driver:

  1. Driver Digit
  2. Driver String
  3. Driver Math
  4. Driver Chinese
// Driver captcha interface for captcha engine to to write staff
type Driver interface {
	//DrawCaptcha draws binary item
	DrawCaptcha(content string) (item Item, err error)
	//GenerateIdQuestionAnswer creates rand id, content and answer
	GenerateIdQuestionAnswer() (id, q, a string)
}

2.2.3 🚴 核心代码captcha.go 🚴

captcha.go 是package的入口文件,源代码逻辑非常简单,如下:

func init() {
	//init rand seed
	rand.Seed(time.Now().UnixNano())
}

// Captcha captcha basic information.
type Captcha struct {
	Driver Driver
	Store  Store
}

func NewCaptcha(driver Driver, store Store) *Captcha {
	return &Captcha{Driver: driver, Store: store}
}

//Generate generates a random id, base64 image string or an error if any
func (c *Captcha) Generate() (id, b64s string, err error) {
	id,content, answer := c.Driver.GenerateIdQuestionAnswer()
	item, err := c.Driver.DrawCaptcha(content)
	if err != nil {
		return "", "", err
	}
	c.Store.Set(id, answer)
	b64s = item.EncodeB64string()
	return
}
//if you has multiple captcha instances which shares a same store. You may want to use `store.Verify` method instead.
//Verify by given id key and remove the captcha value in store, return boolean value.
func (c *Captcha) Verify(id, answer string, clear bool) (match bool) {
	match = c.Store.Get(id, clear) == answer
	return
}

2.2.4 🚵 生成Base64(image/audio)验证码字符串 🚵

//Generate generates a random id, base64 image string or an error if any
func (c *Captcha) Generate() (id, b64s string, err error) {
	id,content, answer := c.Driver.GenerateIdQuestionAnswer()
	item, err := c.Driver.DrawCaptcha(content)
	if err != nil {
		return "", "", err
	}
	c.Store.Set(id, answer)
	b64s = item.EncodeB64string()
	return
}

2.2.5 🤸 校验验证码内容 🤸

//if you has multiple captcha instances which shares a same store. You may want to use `store.Verify` method instead.
//Verify by given id key and remove the captcha value in store, return boolean value.
func (c *Captcha) Verify(id, answer string, clear bool) (match bool) {
	match = c.Store.Get(id, clear) == answer
	return
}

2.2.6 🏃 完整实例代码 🏃

// example of HTTP server that uses the captcha package.
package main

import (
	"encoding/json"
	"fmt"
	"github.com/mojocn/base64Captcha"
	"log"
	"net/http"
)

//configJsonBody json request body.
type configJsonBody struct {
	Id            string
	CaptchaType   string
	VerifyValue   string
	DriverAudio   *base64Captcha.DriverAudio
	DriverString  *base64Captcha.DriverString
	DriverChinese *base64Captcha.DriverChinese
	DriverMath    *base64Captcha.DriverMath
	DriverDigit   *base64Captcha.DriverDigit
}

var store = base64Captcha.DefaultMemStore

// base64Captcha create http handler
func generateCaptchaHandler(w http.ResponseWriter, r *http.Request) {
	//parse request parameters
	decoder := json.NewDecoder(r.Body)
	var param configJsonBody
	err := decoder.Decode(&param)
	if err != nil {
		log.Println(err)
	}
	defer r.Body.Close()
	var driver base64Captcha.Driver

	//create base64 encoding captcha
	switch param.CaptchaType {
	case "audio":
		driver = param.DriverAudio
	case "string":
		driver = param.DriverString.ConvertFonts()
	case "math":
		driver = param.DriverMath.ConvertFonts()
	case "chinese":
		driver = param.DriverChinese.ConvertFonts()
	default:
		driver = param.DriverDigit
	}
	c := base64Captcha.NewCaptcha(driver, store)
	id, b64s, err := c.Generate()
	body := map[string]interface{}{"code": 1, "data": b64s, "captchaId": id, "msg": "success"}
	if err != nil {
		body = map[string]interface{}{"code": 0, "msg": err.Error()}
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	json.NewEncoder(w).Encode(body)
}

// base64Captcha verify http handler
func captchaVerifyHandle(w http.ResponseWriter, r *http.Request) {

	//parse request json body
	decoder := json.NewDecoder(r.Body)
	var param configJsonBody
	err := decoder.Decode(&param)
	if err != nil {
		log.Println(err)
	}
	defer r.Body.Close()
	//verify the captcha
	body := map[string]interface{}{"code": 0, "msg": "failed"}
	if store.Verify(param.Id, param.VerifyValue, true) {
		body = map[string]interface{}{"code": 1, "msg": "ok"}
	}
	//set json response
	w.Header().Set("Content-Type", "application/json; charset=utf-8")

	json.NewEncoder(w).Encode(body)
}

//start a net/http server
func main() {
	//serve Vuejs+ElementUI+Axios Web Application
	http.Handle("/", http.FileServer(http.Dir("./static")))

	//api for create captcha
	http.HandleFunc("/api/getCaptcha", generateCaptchaHandler)

	//api for verify captcha
	http.HandleFunc("/api/verifyCaptcha", captchaVerifyHandle)

	fmt.Println("Server is at :8777")
	if err := http.ListenAndServe(":8777", nil); err != nil {
		log.Fatal(err)
	}
}

2.3 🎬 使用历史版本 🎬

v1.2.2

` go get github.com/mojocn/base64Captcha@v1.2.2 `

3. 🎨 定制自己的图形验证码 🎨

您那个定制自己的图形验码内容,只需实现 interface driver 和 interface item.

下面是几个可以参考的driver实现示例:

  1. DriverMath
  2. DriverChinese
  3. ItemChar

您甚至可以设计captcha struct成您想要的功能

4. 💖 致谢 💖

  • dchest/captha
  • @slayercat
  • @amzyang
  • @Luckyboys

5. 🍭 Licence 🍭

base64Captcha source code is licensed under the Apache Licence, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html).

类别和标签

TCP/IP notes CPP CheatSheet 快速入门 快速查询 Jekyll Github Python Golang Shell Bash docker 教程 container nginx dockerfile golang Spider 钉钉 PhantomJS RPC protobuf gRPC 文件 操作 GUI reflect 网络请求 代理 http chromedp Go进阶 工具 Vim Javascript seaweedfs Raft raft Git Nginx RESTful gitbook node npm github pages toml lumen laravel cors markdown 日志 log mysql 定时任务 php 程序员 Python教程 redis Dockerhub Docker OSS 网络协议 python 运维开发 kali linux 树莓派 raspberryPi algorithm 算法 黑客 hack ssh xtermjs shell context gcc g++ jekyll 破解 LDAP Linux CSS ES6 OAuth2 Prometheus Grafana Nes Game HTTP3 Java 动手 Gateway 网关 Rust笔记
 
8 条评论
未登录用户
@qiaocco
 
qiaocco发表于大约 2 年前
type memoryStore struct {
	sync.RWMutex
	digitsById map[string]string
	idByTime   *list.List
	// Number of items stored since last collection.
	numStored int
	// Number of saved items that triggers collection.
	collectNum int
	// Expiration time of captchas.
	expiration time.Duration
}

idByTime为什么设置成*list.List类型,有什么好处?

@mojocn
 
mojocn发表于大约 2 年前

链表 快速遍历

@Meng-Xin
 
Meng-Xin发表于将近 2 年前

不怎么会用,我这样写,报错了

package tool

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/mojocn/base64Captcha"
)


type CaptchaResult struct {
	Id string `json:"id"`
	Base64Blob	string `json:"base_64_blob"`
	VertifyValue string `json:"vertify_value"`
}
type configJsonBody struct {
	Id            string
	CaptchaType   string
	VerifyValue   string
	DriverAudio   *base64Captcha.DriverAudio
	DriverString  *base64Captcha.DriverString
	DriverChinese *base64Captcha.DriverChinese
	DriverMath    *base64Captcha.DriverMath
	DriverDigit   *base64Captcha.DriverDigit
}

var store = base64Captcha.DefaultMemStore

//生成图形验证码
func GenerateCaptcha(ctx *gin.Context)  {
	//配置项 driver ,param
	var driver base64Captcha.Driver
	var param configJsonBody
	driver = param.DriverString.ConvertFonts()

	//生成图形验证码
	c := base64Captcha.NewCaptcha(driver, store)
	captchaId, b64s, err := c.Generate()
	if err != nil {
		fmt.Println("图形验证码生成失败")
		Failed(ctx,"图形验证码生成失败")
		return
	}
	captchaResult := CaptchaResult{Id:captchaId,Base64Blob: b64s}
	Success(ctx,map[string]interface{}{
		"captcha_result" : captchaResult,
	})
}
@Meng-Xin
 
Meng-Xin发表于将近 2 年前

请问取消了 configCharacter 这个配置方法以后,怎么自定义一个验证码呢?

@mojocn
 
mojocn发表于将近 2 年前

@Meng-Xin
请问取消了 configCharacter 这个配置方法以后,怎么自定义一个验证码呢?

driver = param.DriverString.ConvertFonts() 中的 全部字段填充就可以了

@BuxsRen
 
BuxsRen发表于超过 1 年前

完整实例运行直接报:
runtime error: invalid memory address or nil pointer dereference

@GuoXiaoBo
 
GuoXiaoBo发表于超过 1 年前

https://github.com/mojocn/base64Captcha/blob/develop/interface_store.go

最新的1.3.5为什么Get&Verify 仍然不返回error呢,redis store情况下不太友好吧~

@xiwang007
 
xiwang007发表于超过 1 年前

作者给的demo需要一点web基础,我这边直接把web相关的抽离了,只剩下验证码的生成和验证给大家交流学习.

创建一个mycaptcha_test.go的文件

然后运行里面TestMyCaptcha单元测试函数

package mycaptcha

import (
	"log"
	"testing"

	"github.com/mojocn/base64Captcha"
)

//configJsonBody json request body.
type configJsonBody struct {
	Id            string
	CaptchaType   string
	VerifyValue   string
	DriverAudio   *base64Captcha.DriverAudio
	DriverString  *base64Captcha.DriverString
	DriverChinese *base64Captcha.DriverChinese
	DriverMath    *base64Captcha.DriverMath
	DriverDigit   *base64Captcha.DriverDigit
}

var store = base64Captcha.DefaultMemStore

// base64Captcha create return id, b64s, err
func GetCaptcha() (string, string, error) {

	// 	{
	// 		ShowLineOptions: [],
	// 		CaptchaType: "string",
	// 		Id: '',
	// 		VerifyValue: '',
	// 		DriverAudio: {
	// 			Length: 6,
	// 			Language: 'zh'
	// 		},
	// 		DriverString: {
	// 			Height: 60,
	// 			Width: 240,
	// 			ShowLineOptions: 0,
	// 			NoiseCount: 0,
	// 			Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
	// 			Length: 6,
	// 			Fonts: ["wqy-microhei.ttc"],
	// 			BgColor: {R: 0, G: 0, B: 0, A: 0},
	// 		},
	// 		DriverMath: {
	// 			Height: 60,
	// 			Width: 240,
	// 			ShowLineOptions: 0,
	// 			NoiseCount: 0,
	// 			Length: 6,
	// 			Fonts: ["wqy-microhei.ttc"],
	// 			BgColor: {R: 0, G: 0, B: 0, A: 0},
	// 		},
	// 		DriverChinese: {
	// 			Height: 60,
	// 			Width: 320,
	// 			ShowLineOptions: 0,
	// 			NoiseCount: 0,
	// 			Source: "设想,你在,处理,消费者,的音,频输,出音,频可,能无,论什,么都,没有,任何,输出,或者,它可,能是,单声道,立体声,或是,环绕立,体声的,,不想要,的值",
	// 			Length: 2,
	// 			Fonts: ["wqy-microhei.ttc"],
	// 			BgColor: {R: 125, G: 125, B: 0, A: 118},
	// 		},
	// 		DriverDigit: {
	// 			Height: 80,
	// 			Width: 240,
	// 			Length: 5,
	// 			MaxSkew: 0.7,
	// 			DotCount: 80
	// 		}
	// 	},
	// 	blob: "",
	// 	loading: false
	// }

	// https://captcha.mojotv.cn/ 调试配置
	var param configJsonBody = configJsonBody{
		Id:          "",
		CaptchaType: "string",
		VerifyValue: "",
		DriverAudio: &base64Captcha.DriverAudio{},
		DriverString: &base64Captcha.DriverString{
			Length:          4,
			Height:          60,
			Width:           240,
			ShowLineOptions: 2,
			NoiseCount:      0,
			Source:          "1234567890qwertyuioplkjhgfdsazxcvbnm",
		},
		DriverChinese: &base64Captcha.DriverChinese{},
		DriverMath:    &base64Captcha.DriverMath{},
		DriverDigit:   &base64Captcha.DriverDigit{},
	}
	var driver base64Captcha.Driver

	//create base64 encoding captcha
	switch param.CaptchaType {
	case "audio":
		driver = param.DriverAudio
	case "string":
		driver = param.DriverString.ConvertFonts()
	case "math":
		driver = param.DriverMath.ConvertFonts()
	case "chinese":
		driver = param.DriverChinese.ConvertFonts()
	default:
		driver = param.DriverDigit
	}
	c := base64Captcha.NewCaptcha(driver, store)
	return c.Generate()
	// id, b64s, err := c.Generate()

	// body := map[string]interface{}{"code": 1, "data": b64s, "captchaId": id, "msg": "success"}
	// if err != nil {
	// 	body = map[string]interface{}{"code": 0, "msg": err.Error()}
	// }
	// var _ = body

	// // log.Println(body)
	// log.Println(1)
	// log.Println(id)

	// log.Printf("store =%+v\n", store)
}

// base64Captcha verify
func VerifyCaptcha(id, VerifyValue string) bool {
	return store.Verify(id, VerifyValue, true)
}

func TestMyCaptcha(t *testing.T) {
	id, b64s, err := GetCaptcha()
	if err != nil {
		return
	}
	var _ = b64s
	log.Println("id =", id)
	log.Println("VerifyValue =", store.Get(id, true))
	result := VerifyCaptcha(id, store.Get(id, true))
	log.Println("result =", result)
}

https://mojotv.cn/go/refactor-base64-captcha