#Openresty(Lua/Luajit)生成验证码
哎呀呀,好长时间没写博客了,最近都干啥去了呢?
开始吧,我在很认真的研究我的博客该使用什么样的验证码,我不能让我的博客总是不开启评论功能吧?要开启评论功能总是有坏人发广告怎么办啊?这年头没验证码,我这个博客一开启评论功能或者留言板功能就没法活了……
一开始我的想法太天真了,我想实现一个别人没用过的,高大上吊炸天炫拽酷的——动态验证码~我想实现的这个动态验证码可不是徒有其表,我的想法是,一定要动起来,才能认出里面的字符来,如果不动,你看到的就是一片雪花点,就像电视没信号的感觉……
真有这种东西么?确实是有的……我也是受一个叫做`Lost_In_The_Static`的游戏的启发才想到要实现这么个验证码……所以,那还等什么,赶紧动手吧!
选什么库呢?看网上都用`lua-gd库`,那我也用`lua-gd库`吧,安装就照着[网上的教程](https://yq.aliyun.com/articles/25821 "lua-gd网上的教程")就去搞了……网上的教程好麻烦,其实事实上哪有这么繁琐……
```bash
yum install gd
yum install gd-devel
```
它自动就会把依赖库全给装上了~
然后说说`Lua-gd`的编译安装,下载什么的不说了,`makefile`大约照这么写就行了(注:我的是`64位`的`CentOS`)
```makefile
CC=gcc
OUTFILE=gd.so
CFLAGS=-Wall `gdlib-config --cflags` -I/usr/local/openresty/luajit/include/luajit-2.1 -O3
GDFEATURES=`gdlib-config --features |sed -e "s/GD_/-DGD_/g"`
LFLAGS=-shared `gdlib-config --ldflags` `gdlib-config --libs` -L/usr/lib64 -L/usr/local/openresty/luajit/lib -lluajit-5.1 -lgd
INSTALL_PATH=/usr/local/openresty/luajit/lib
all: $(OUTFILE)
$(OUTFILE): luagd.c
$(CC) -fPIC -o $(OUTFILE) $(GDFEATURES) $(CFLAGS) $(LFLAGS) luagd.c
lua test_features.lua
install: $(OUTFILE)
install -s $(OUTFILE) $(INSTALL_PATH)
clean:
rm -f $(OUTFILE) *.o
```
为了保险起见,我在`/usr/local/openresty/lualib`和`/usr/local/openresty/luajit/lib`都复制了一份编译出来的`gd.so`
然后,我就遇到了第一个坑:**[Lua-gd示例](http://ittner.github.io/lua-gd/manual.html "Lua-gd示例")里面生成gif动图的示例是错的?!**
也许生成动图这个功能用的人真的不多?但是,我在发现这个问题之前浪费了不少的功夫去研究到底是哪儿出了问题:为什么示例程序能够生成成功,而我的却总是生成失败……
后来我终于发现了原因:lua-gd的示例程序`GIF animation`中,有一行是错的,那就是
```lua
fp = io.open("out.gif", "w")
```
应该改成
```lua
fp = io.open("out.gif", "wb")
```
严谨的程絮猿们都会懂得这并不仅仅是少个`b`而已,这就是bug,少了这个`b`仅仅会使少量运气好的gif能够生成成功……而示例程序中这个就是运气太好了才没发现这个bug……而我却总是踩地雷才发现的这个bug……
然后是第二个坑:*lua-gd里面没有提供控制线的粗细的方法,也没有提供画曲线的方法*
这个倒不是什么重点,因为,画粗线,你用多边形啊~画曲线?你有种去控制像素自己画吧~:sweat:
然后就是生成一个验证码试试呗?然后我就生成了个……然后,效果好差啊……:confounded:
像下面这种:
**注:千万不要长期盯着下面这张动图试图分辨里面的字到底是啥,会特别头晕!!:scream:很痛苦!!切记!!切记!!不听劝告者后果自负!!!!**
![动态验证码尝试1](gifCheckNum1.gif "动态验证码尝试1")
不要再看了,会头晕的,真的……我冥思苦想啊……是不是我生成方式不对?然后我又尝试了其他运动方式……
**注:千万不要长期盯着下面这张动图试图分辨里面的字到底是啥,会特别头晕!!:scream:很痛苦!!切记!!切记!!不听劝告者后果自负!!!!**
![动态验证码尝试2](gifCheckNum2.gif "动态验证码尝试2")
呃……不要看了……一样很失败……
后来我的动态验证码计划算是破产了……由于代码很乱,个人认为算法的生成效率也低,我也不想再整理了,所以,这个就不再分享了……
然后,由于受到了打击,在此期间我就干了点别的,学习了一个WebGL,又学了学口琴(这个如果可能的话,会在以后的博客中介绍一下,然而这不是这次博客的重点……)
终于,最近两天,我又捡起了验证码这件事……(*为了写这个验证码今晚上都跟妹纸关系紧张了……2017.04.26*)
这个时候我感觉我累了,我要安安分分的写个板正的验证码,再也不要炫酷了……哪怕很low,我总归要给我的博客搞个验证码嘛……
于是今天晚上反复修改,终于完成了一版验证码:
```lua
--2017-04-28 00:45 last modified
math.randomseed(ngx.now()*1000%1e+8)
local gd = require "gd"
local rnd = math.random
local x,y=150,70
local im = gd.createTrueColor(x, y)
local dict="ABCDEFGHKLMNPQRSTUVWXYZabdehkmnrstuvwxz23456789"
local fonts = {"Arial","Arial:bold","Arial:italic","Arial:bold:italic","Times New Roman","Comic Sans MS"};
local function randomColors(im)
local colors = {}
for i=1,8 do
local gray = i*25+50
colors[i] = im:colorAllocate(gray,gray,gray)
end
return colors;
end
local colors = randomColors(im)
local function shuffleColorGenerator(colors)
local num = #colors;
local arr = {};
for i = 1,num do
arr[i] = i;
end
for i = 1,num do
local rndn = rnd(num)
local temp = arr[i]
arr[i] = arr[rndn]
arr[rndn] = temp
end
local iterator = 1
return (function()
local color = colors[arr[iterator]]
iterator = iterator + 1;
if iterator > num then
iterator=1
end
return color
end)
end
local shuffleColor = shuffleColorGenerator(colors)
local function getRandColor()
return colors[rnd(#colors)]
end
im:filledRectangle(0, 0, x, y, getRandColor())
local bgx = rnd(x/3)+math.floor(x/3);
local bgy = rnd(y/3)+math.floor(y/3);
im:filledRectangle(rnd(10), rnd(10), bgx-rnd(10), bgy-rnd(10), shuffleColor())
im:filledRectangle(rnd(10), bgy-rnd(10), bgx-rnd(10), y-rnd(10), shuffleColor())
im:filledRectangle(bgx-rnd(10), rnd(10), x-rnd(10), bgy-rnd(10), shuffleColor())
im:filledRectangle(bgx-rnd(10), bgy-rnd(10), x-rnd(10), y-rnd(10), shuffleColor())
gd.useFontConfig(true)
for i=1,15 do
local py = rnd(y);
im:line(0,py,x,py,getRandColor())
end
for i=1,25 do
local px = rnd(x)
im:line(px,0,px,y,getRandColor())
end
local result = {}
for i=1,4 do
local w = rnd(#dict)
result[i] = string.sub(dict,w,w)
im:stringFT(shuffleColor(), fonts[rnd(#fonts)], 25+rnd(10), math.rad(rnd(60)-30), rnd(15)+30*(i-1)+10, rnd(20)+math.floor(y*0.5), result[i])
end
for i=1,5 do
local py = rnd(y);
im:line(0,py,x,py,getRandColor())
end
for i=1,10 do
local px = rnd(x)
im:line(px,0,px,y,getRandColor())
end
ngx.header.content_type="image/png"
ngx.header.result = table.concat(result)
ngx.say(im:pngStr())
ngx.exit(200)
--im:png("test.png")
```
生成的效果大约是这样的:
![板正的验证码](../checkNumTest.do "板正的验证码")
对于这个验证码我要说这么几点:
- 这个验证码本来是彩色的,后来觉得对色彩障碍的人太不友好了……所以改成灰色的了……因为细微的色彩差距的分辨对于计算来说不算什么:bulb:,但是它却能阻挡10%以上的人类……:confounded:
- 这个验证码用了8个灰度,在实际验证中发现靠近黑色的灰度辨认效果很差,于是我就把灰度整个偏离黑色50,以提高显示效果:ok_hand:
- 其中去除了很多难以分辨的字母,方便用户辨认,可以根据需要增添字符:smile:
- windows版本下的lua-gd好像找不到这些FontConfig字体,所以大概只能在Linux下找到这些字体吧?:fearful:
- 如果觉得识别度太高或者太低,请自行调节其中的参数:wink:
- 为方便确认验证码中的字符,我把验证码所表示的字符放到报文头部的result字段了,因为当前只是个验证程序而已,还没真正的应用,大家可以根据报文头部的result字段来判断该验证码的识别率,去掉不太好用的字符:hushed:
- 呃……这个代码就不用我再强行解释是怎么工作的了吧?:stuck_out_tongue:
- 等等,上面那个验证码效果图难道是真的?!为啥每次刷新都不一样?!嗯,是的,你猜对了!这就是用那个程序生成的真正的效果……还要我说什么好呢?你懂得~:smirk: