我们用GO玩一下验证码
嗨,我是小魔童哪吒,咱们上次分享的GO 中 defer
的实现原理,再来回顾一下吧
- 分享了defer是什么
- 简单示意了栈和队列
- defer的数据结构和实现原理,具体的源码展示
- GO 中
defer
的 3 条规则
要是对 GO 中 defer
实现原理还有点兴趣的话,欢迎查看文章 GO 中 defer的实现原理
今天我们来分享一些使用 GO
实现小案例,咱们边玩边成长
GO 的验证码介绍
我们平时使用到的验证码大致分为这几种,咱们梳理一下:
- 传统输入的形式
输入图片上的数字,文字,字母等等
- 输入类型的图形验证码
这个主要是来打广告的
- 纯行为验证码
例如,按照提示滑动等等
- 图标选择与行为辅助的验证码
例如咱们买火车票的时候验证码,各种图标让你选
- 点击式的图文验证与行为辅助
例如某宝的验证码
- 智能验证码
例如,点触智能验证码
GO 验证码案例
我们今天就来玩一玩第一种,使用最多的一种验证码吧
会使用 GO 的这个验证码库来完成,github.com/dchest/captcha
若我们向C/C++
一样,将很多的底层处理都是我们自己来封装来实现的话,那还是挺累人的,GO 这一点确实蛮好,有很多使用的包,咱们在使用之余,也可以站在巨人的肩膀人,学习源码中的实现方式,学习大佬们的设计思想。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lC6WxucK-1625666538870)(https://i.loli.net/2021/06/19/q1joYsKZCSmT7Ep.gif)]
安装captcha
库
大家使用如下命令就可以下载下来使用
go get github.com/dchest/captcha
当我们在GOLAND
中用到 captcha
库的时候,咱们可以看看源码目录
源码目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAmTlVq9-1625666538873)(https://i.loli.net/2021/06/18/na2EQkyJZtxi1Pw.png)]
- 有源码的具体使用案例
- 具体的示例图片
- 相关音频处理的实现
- 验证码处理的实现
- 图片处理的实现
- 随机数处理的实现方式
- 等等…
支持的语言
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jiT2gDwt-1625666538877)(https://i.loli.net/2021/06/18/3svjDucamYOJp8n.png)]
这个库目前支持的音频有 4 种语言:
- 英文
- 中文
- 俄文
- 日文
验证码默认参数
库中验证码的大小默认是 宽 240 px,高 80 px
在源码中的 image.go
const ( // Standard width and height of a captcha image. StdWidth = 240 StdHeight = 80 // Maximum absolute skew factor of a single digit. maxSkew = 0.7 // Number of background circles. circleCount = 20 ) type Image struct { *image.Paletted numWidth int numHeight int dotSize int rng siprng }
随机数包含的字符
如下是验证码id中允许的字符 ,可以在源码中看到
源码包中的 random.go
// idChars are characters allowed in captcha id. var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
关于音频的处理
在源码的 sounds.go
文件
目前对于音频只支持 4 种语言,"en", "ja", "ru", "zh".
/ NewAudio returns a new audio captcha with the given digits, where each digit // must be in range 0-9. Digits are pronounced in the given language. If there // are no sounds for the given language, English is used. // // Possible values for lang are "en", "ja", "ru", "zh". func NewAudio(id string, digits []byte, lang string) *Audio { a := new(Audio) // Initialize PRNG. a.rng.Seed(deriveSeed(audioSeedPurpose, id, digits)) if sounds, ok := digitSounds[lang]; ok { a.digitSounds = sounds } else { a.digitSounds = digitSounds["en"] } numsnd := make([][]byte, len(digits)) nsdur := 0 for i, n := range digits { snd := a.randomizedDigitSound(n) nsdur += len(snd) numsnd[i] = snd } // Random intervals between digits (including beginning). intervals := make([]int, len(digits)+1) intdur := 0 for i := range intervals { dur := a.rng.Int(sampleRate, sampleRate*3) // 1 to 3 seconds intdur += dur intervals[i] = dur } // Generate background sound. bg := a.makeBackgroundSound(a.longestDigitSndLen()*len(digits) + intdur) // Create buffer and write audio to it. sil := makeSilence(sampleRate / 5) bufcap := 3*len(beepSound) + 2*len(sil) + len(bg) + len(endingBeepSound) a.body = bytes.NewBuffer(make([]byte, 0, bufcap)) // Write prelude, three beeps. a.body.Write(beepSound) a.body.Write(sil) a.body.Write(beepSound) a.body.Write(sil) a.body.Write(beepSound) // Write digits. pos := intervals[0] for i, v := range numsnd { mixSound(bg[pos:], v) pos += len(v) + intervals[i+1] } a.body.Write(bg) // Write ending (one beep). a.body.Write(endingBeepSound) return a }
其中关于语言的数据在digitSounds
map 中
看到这个,就有点像做字库解析 和 嵌入式里面的数据解析了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGRr0BH4-1625666538879)(https://i.loli.net/2021/06/19/A4mw2FGn6Itu8ch.png)]
var digitSounds = map[string][][]byte{ "en": [][]byte{ { // 0 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, ... }, "ru": [][]byte{ { // 0 0x7f, 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, 0x7f, 0x7e, 0x7f, 0x7f, 0x7e, ... }, "zh": [][]byte{ { // 0 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x7f, 0x80, 0x7f, 0x80, 0x7f, 0x80, ... }, "ja": [][]byte{ { // 0 0x7f, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x83, ... },
开始案例的演示
my_captcha.html 实现如下
暂时关于音频的语言,就写了2种语言
- 英文
- 中文
<!doctype html> <head> <title>GO 简单制作验证码案例</title> <style> input{ margin-top: 30px; } </style> </head> <body> <script> // 设置语言 function setSrcQuery(e, q) { var src = e.src; var p = src.indexOf('?'); if (p >= 0) { src = src.substr(0, p); } e.src = src + "?" + q } // 播放音频 function playAudio() { var le = document.getElementById("lang"); var lang = le.options[le.selectedIndex].value; var e = document.getElementById('audio') setSrcQuery(e, "lang=" + lang) e.style.display = 'block'; e.autoplay = 'true'; return false; } // 切换语言 function changeLang() { var e = document.getElementById('audio') if (e.style.display == 'block') { playAudio(); } } // 重新加载 function reload() { setSrcQuery(document.getElementById('image'), "reload=" + (new Date()).getTime()); setSrcQuery(document.getElementById('audio'), (new Date()).getTime()); return false; } </script> <div align="center" > <select id="lang" onchange="changeLang()"> <option value="en">英文</option> <option value="zh">中文</option> </select> </div> <form action="/processCapcha" method=post align="center"> <p>请输入你在下面的图片中看到的数字:</p> <p><img id=image src="/captcha/{{.CaptchaId}}.png" alt="Captcha image"></p> <a href="#" onclick="reload()">重新加载</a> | <a href="#" onclick="playAudio()">播放音频验证码</a> <audio id=audio controls style="display:none" src="/captcha/{{.CaptchaId}}.wav" preload=none> You browser doesn't support audio. <a href="/captcha/download/{{.CaptchaId}}.wav">下载文件</a> to play it in the external player. </audio> <input type=hidden name=captchaId value="{{.CaptchaId}}" align=center><br> <input name=captchaSolution align=center> <input type=submit value=Submit> </form>
main.go
- 展示验证码
- 处理验证码,结果展示
- 重载验证码
- 播放验证码音频
package main import ( "github.com/dchest/captcha" "io" "io/ioutil" "log" "net/http" "text/template" ) const filePath = "./my_captcha.html" // 读取 html 文件 func readHtml() string { var bytes []byte var err error if bytes, err = ioutil.ReadFile(filePath); err != nil { log.Fatalf("ioutil.ReadFile error filePath = %s , err :"+filePath, err) return "" } return string(bytes) } // 读取html 文件,转成template.Template 指针 var formTemplate = template.Must(template.New("myCaptcha").Parse(readHtml())) // 显示验证码 func showCaptcha(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } d := struct { CaptchaId string }{ captcha.New(), } // Execute将解析后的模板应用到指定的数据对象,并将输出写入wr if err := formTemplate.Execute(w, &d); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } // 处理验证码,跳转结果页面 func resultPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if !captcha.VerifyString(r.FormValue("captchaId"), r.FormValue("captchaSolution")) { io.WriteString(w, "错误的验证码,请重新输入\n") } else { io.WriteString(w, "验证吗正确,你很棒哦!!\n") } io.WriteString(w, "<br><a href='/'>再试一下</a>") } func main() { // 简单设置log参数 log.SetFlags(log.Lshortfile | log.LstdFlags) http.HandleFunc("/", showCaptcha) http.HandleFunc("/processCapcha", resultPage) http.Handle("/captcha/", captcha.Server(captcha.StdWidth, captcha.StdHeight)) log.Println("starting server : 8888") if err := http.ListenAndServe("localhost:8888", nil); err != nil { log.Fatal(err) } }
上述代码的宽高 是这样的
StdWidth = 240 StdHeight = 80
上述 HandleFunc
的回调函数是这个样子的,之前介绍 gin 的时候有分享过, 可以回头看看 文章 来我们一起探究一下net/http 的代码流程
// HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
验证码实际效果
点击播放音频验证码的时候,可以看到这样的效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZDXadhF-1625666538884)(https://i.loli.net/2021/06/19/HapmAjkCbiXRz28.png)]
该音频,会根据我们选择语言,来播放不同的语音,读取图片上的数字
总结
- 验证码种类梳理
- 验证码库的安装
- 验证码库的源码介绍
- 实操,编码
- 验证码效果展示
欢迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里,下一次 如何使用GOLANG发送邮件
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是小魔童哪吒,欢迎点赞关注收藏,下次见~