验证码代码解读 - 蝈蝈俊

时间:2024-04-14 21:47:29

下面解读的验证码代码来自: http://www.oschina.net/code/snippet_173630_12006

这个验证码的效果类似如下下图:

image

这个验证码包含下面三层元素

  • 随机大小和颜色的10个点
  • 4位数字的验证码(随机偏转方向、每个点间距随机)
  • 一条类似删除线的干扰线

对应的带代码注释的源文件如下:

   1: package main
   2:  
   3: import (
   4:     crand "crypto/rand"
   5:     "fmt"
   6:     "image"
   7:     "image/color"
   8:     "image/png"
   9:     "io"
  10:     "math/rand"
  11:     "net/http"
  12:     "strconv"
  13:     "time"
  14: )
  15:  
  16: const (
  17:     stdWidth  = 100 // 固定图片宽度
  18:     stdHeight = 40  // 固定图片高度
  19:     maxSkew   = 2
  20: )
  21:  
  22: // 字体常量信息
  23: const (
  24:     fontWidth  = 5 // 字体的宽度
  25:     fontHeight = 8 // 字体的高度
  26:     blackChar  = 1
  27: )
  28:  
  29: // 简化期间使用的字体库
  30: var font = [][]byte{
  31:     { // 0
  32:         0, 1, 1, 1, 0,
  33:         1, 0, 0, 0, 1,
  34:         1, 0, 0, 0, 1,
  35:         1, 0, 0, 0, 1,
  36:         1, 0, 0, 0, 1,
  37:         1, 0, 0, 0, 1,
  38:         1, 0, 0, 0, 1,
  39:         0, 1, 1, 1, 0},
  40:     { // 1
  41:         0, 0, 1, 0, 0,
  42:         0, 1, 1, 0, 0,
  43:         1, 0, 1, 0, 0,
  44:         0, 0, 1, 0, 0,
  45:         0, 0, 1, 0, 0,
  46:         0, 0, 1, 0, 0,
  47:         0, 0, 1, 0, 0,
  48:         1, 1, 1, 1, 1},
  49:     { // 2
  50:         0, 1, 1, 1, 0,
  51:         1, 0, 0, 0, 1,
  52:         0, 0, 0, 0, 1,
  53:         0, 0, 0, 1, 1,
  54:         0, 1, 1, 0, 0,
  55:         1, 0, 0, 0, 0,
  56:         1, 0, 0, 0, 0,
  57:         1, 1, 1, 1, 1},
  58:     { // 3
  59:         1, 1, 1, 1, 0,
  60:         0, 0, 0, 0, 1,
  61:         0, 0, 0, 1, 0,
  62:         0, 1, 1, 1, 0,
  63:         0, 0, 0, 1, 0,
  64:         0, 0, 0, 0, 1,
  65:         0, 0, 0, 0, 1,
  66:         1, 1, 1, 1, 0},
  67:     { // 4
  68:         1, 0, 0, 1, 0,
  69:         1, 0, 0, 1, 0,
  70:         1, 0, 0, 1, 0,
  71:         1, 0, 0, 1, 0,
  72:         1, 1, 1, 1, 1,
  73:         0, 0, 0, 1, 0,
  74:         0, 0, 0, 1, 0,
  75:         0, 0, 0, 1, 0},
  76:     { // 5
  77:         1, 1, 1, 1, 1,
  78:         1, 0, 0, 0, 0,
  79:         1, 0, 0, 0, 0,
  80:         1, 1, 1, 1, 0,
  81:         0, 0, 0, 0, 1,
  82:         0, 0, 0, 0, 1,
  83:         0, 0, 0, 0, 1,
  84:         1, 1, 1, 1, 0},
  85:     { // 6
  86:         0, 0, 1, 1, 1,
  87:         0, 1, 0, 0, 0,
  88:         1, 0, 0, 0, 0,
  89:         1, 1, 1, 1, 0,
  90:         1, 0, 0, 0, 1,
  91:         1, 0, 0, 0, 1,
  92:         1, 0, 0, 0, 1,
  93:         0, 1, 1, 1, 0},
  94:     { // 7
  95:         1, 1, 1, 1, 1,
  96:         0, 0, 0, 0, 1,
  97:         0, 0, 0, 0, 1,
  98:         0, 0, 0, 1, 0,
  99:         0, 0, 1, 0, 0,
 100:         0, 1, 0, 0, 0,
 101:         0, 1, 0, 0, 0,
 102:         0, 1, 0, 0, 0},
 103:     { // 8
 104:         0, 1, 1, 1, 0,
 105:         1, 0, 0, 0, 1,
 106:         1, 0, 0, 0, 1,
 107:         0, 1, 1, 1, 0,
 108:         1, 0, 0, 0, 1,
 109:         1, 0, 0, 0, 1,
 110:         1, 0, 0, 0, 1,
 111:         0, 1, 1, 1, 0},
 112:     { // 9
 113:         0, 1, 1, 1, 0,
 114:         1, 0, 0, 0, 1,
 115:         1, 0, 0, 0, 1,
 116:         1, 1, 0, 0, 1,
 117:         0, 1, 1, 1, 1,
 118:         0, 0, 0, 0, 1,
 119:         0, 0, 0, 0, 1,
 120:         1, 1, 1, 1, 0},
 121: }
 122:  
 123: type Image struct {
 124:     *image.NRGBA
 125:     color   *color.NRGBA
 126:     width   int //a digit width
 127:     height  int //a digit height
 128:     dotsize int
 129: }
 130:  
 131: func init() {
 132:     // 打乱随机种子
 133:     rand.Seed(int64(time.Second))
 134:  
 135: }
 136:  
 137: // 产生指定长宽的图片
 138: func NewImage(digits []byte, width, height int) *Image {
 139:     img := new(Image)
 140:     r := image.Rect(img.width, img.height, stdWidth, stdHeight)
 141:     img.NRGBA = image.NewNRGBA(r)
 142:     img.color = &color.NRGBA{
 143:         uint8(rand.Intn(129)),
 144:         uint8(rand.Intn(129)),
 145:         uint8(rand.Intn(129)),
 146:         0xFF}
 147:  
 148:     img.calculateSizes(width, height, len(digits))
 149:  
 150:     // Draw background (10 random circles of random brightness)
 151:     // 画背景, 10个随机亮度的园
 152:     img.fillWithCircles(10, img.dotsize)
 153:     maxx := width - (img.width+img.dotsize)*len(digits) - img.dotsize
 154:     maxy := height - img.height - img.dotsize*2
 155:     x := rnd(img.dotsize*2, maxx)
 156:     y := rnd(img.dotsize*2, maxy)
 157:  
 158:     // Draw digits. 画验证码
 159:     for _, n := range digits {
 160:         img.drawDigit(font[n], x, y)
 161:         x += img.width + img.dotsize // 下一个验证码字符的起始位置
 162:     }
 163:  
 164:     // Draw strike-through line. 画类似删除线的干扰线
 165:     img.strikeThrough()
 166:     return img
 167:  
 168: }
 169:  
 170: func (img *Image) WriteTo(w io.Writer) (int64, error) {
 171:     return 0, png.Encode(w, img)
 172:  
 173: }
 174:  
 175: // 计算几个要显示字符的尺寸,没有开始绘画。
 176: func (img *Image) calculateSizes(width, height, ncount int) {
 177:  
 178:     // Goal: fit all digits inside the image.
 179:     var border int // 边距
 180:     if width > height {
 181:         border = height / 5
 182:     } else {
 183:         border = width / 5
 184:     }
 185:     // Convert everything to floats for calculations.
 186:     w := float64(width - border*2)  //  100 - 8*2=84
 187:     h := float64(height - border*2) //  40 - 8*2 = 24
 188:     fmt.Println("ddd%v;%v", w, h)
 189:  
 190:     // fw takes into account 1-dot spacing between digits.
 191:     fw := float64(fontWidth) + 1 // 6
 192:     fh := float64(fontHeight)    // 8
 193:     nc := float64(ncount)        // 4
 194:     fmt.Println("eee%v;%v;%v", fw, fh, nc)
 195:  
 196:     // Calculate the width of a single digit taking into account only the
 197:     // width of the image.
 198:     nw := w / nc //  84/ 4 = 21
 199:  
 200:     // Calculate the height of a digit from this width.
 201:     nh := nw * fh / fw //  21*8/6 = 28
 202:  
 203:     // Digit too high?
 204:     if nh > h {
 205:         // Fit digits based on height.
 206:         nh = h            // nh = 24
 207:         nw = fw / fh * nh // 6 / 8 * 24 = 18
 208:     }
 209:  
 210:     // Calculate dot size.
 211:     img.dotsize = int(nh / fh) // 24/8 = 3
 212:  
 213:     // Save everything, making the actual width smaller by 1 dot to account
 214:     // for spacing between digits.
 215:     img.width = int(nw)                // 18
 216:     img.height = int(nh) - img.dotsize // 24 - 3 = 21
 217:  
 218:     fmt.Printf("format:%v;%v;%v/r/n", img.dotsize, img.width, img.height)
 219: }
 220:  
 221: // 随机画指定个数个圆点
 222: func (img *Image) fillWithCircles(n, maxradius int) {
 223:     color := img.color
 224:     maxx := img.Bounds().Max.X
 225:     maxy := img.Bounds().Max.Y
 226:     for i := 0; i < n; i++ {
 227:         setRandomBrightness(color, 255) // 随机颜色亮度
 228:         r := rnd(1, maxradius)          // 随机大小
 229:         img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r)
 230:     }
 231:  
 232: }
 233:  
 234: // 画 水平线
 235: func (img *Image) drawHorizLine(color color.Color, fromX, toX, y int) {
 236:     // 遍历画每个点
 237:     for x := fromX; x <= toX; x++ {
 238:         img.Set(x, y, color)
 239:     }
 240:  
 241: }
 242:  
 243: // 画指定颜色的实心圆
 244: func (img *Image) drawCircle(color color.Color, x, y, radius int) {
 245:     f := 1 - radius
 246:     dfx := 1
 247:     dfy := -2 * radius
 248:     xx := 0
 249:     yy := radius
 250:     img.Set(x, y+radius, color)
 251:     img.Set(x, y-radius, color)
 252:     img.drawHorizLine(color, x-radius, x+radius, y)
 253:     for xx < yy {
 254:         if f >= 0 {
 255:             yy--
 256:             dfy += 2
 257:             f += dfy
 258:         }
 259:         xx++
 260:         dfx += 2
 261:         f += dfx
 262:         img.drawHorizLine(color, x-xx, x+xx, y+yy)
 263:         img.drawHorizLine(color, x-xx, x+xx, y-yy)
 264:         img.drawHorizLine(color, x-yy, x+yy, y+xx)
 265:         img.drawHorizLine(color, x-yy, x+yy, y-xx)
 266:     }
 267:  
 268: }
 269:  
 270: // 画一个随机干扰线
 271: func (img *Image) strikeThrough() {
 272:     r := 0
 273:     maxx := img.Bounds().Max.X
 274:     maxy := img.Bounds().Max.Y
 275:     y := rnd(maxy/3, maxy-maxy/3)
 276:     for x := 0; x < maxx; x += r {
 277:         r = rnd(1, img.dotsize/3)
 278:         y += rnd(-img.dotsize/2, img.dotsize/2)
 279:         if y <= 0 || y >= maxy {
 280:             y = rnd(maxy/3, maxy-maxy/3)
 281:         }
 282:         img.drawCircle(img.color, x, y, r)
 283:     }
 284:  
 285: }
 286:  
 287: // 画指定的验证码其中一个字符
 288: func (img *Image) drawDigit(digit []byte, x, y int) {
 289:     // 随机偏转方向
 290:     skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew))
 291:     xs := float64(x)
 292:     minr := img.dotsize / 2               // minumum radius
 293:     maxr := img.dotsize/2 + img.dotsize/4 // maximum radius
 294:     y += rnd(-minr, minr)
 295:     for yy := 0; yy < fontHeight; yy++ {
 296:         for xx := 0; xx < fontWidth; xx++ {
 297:             if digit[yy*fontWidth+xx] != blackChar {
 298:                 continue
 299:             }
 300:             // Introduce random variations.
 301:             // 引入一些随机变化,不过这里变化量非常小
 302:             or := rnd(minr, maxr)
 303:             ox := x + (xx * img.dotsize) + rnd(0, or/2)
 304:             oy := y + (yy * img.dotsize) + rnd(0, or/2)
 305:             img.drawCircle(img.color, ox, oy, or)
 306:         }
 307:         xs += skf
 308:         x = int(xs)
 309:     }
 310:  
 311: }
 312:  
 313: // 设置随机颜色亮度
 314: func setRandomBrightness(c *color.NRGBA, max uint8) {
 315:     minc := min3(c.R, c.G, c.B)
 316:     maxc := max3(c.R, c.G, c.B)
 317:     if maxc > max {
 318:         return
 319:     }
 320:     n := rand.Intn(int(max-maxc)) - int(minc)
 321:     c.R = uint8(int(c.R) + n)
 322:     c.G = uint8(int(c.G) + n)
 323:     c.B = uint8(int(c.B) + n)
 324:  
 325: }
 326:  
 327: // 三个数中的最小数
 328: func min3(x, y, z uint8) (o uint8) {
 329:     o = x
 330:     if y < o {
 331:         o = y
 332:     }
 333:     if z < o {
 334:         o = z
 335:     }
 336:     return
 337:  
 338: }
 339:  
 340: // 三个数的最大数
 341: func max3(x, y, z uint8) (o uint8) {
 342:     o = x
 343:     if y > o {
 344:         o = y
 345:     }
 346:     if z > o {
 347:         o = z
 348:     }
 349:     return
 350:  
 351: }
 352:  
 353: // rnd returns a random number in range [from, to].
 354: // 然会指定范围的随机数
 355: func rnd(from, to int) int {
 356:     //println(to+1-from)
 357:     return rand.Intn(to+1-from) + from
 358:  
 359: }
 360:  
 361: const (
 362:     // Standard length of uniuri string to achive ~95 bits of entropy.
 363:     StdLen = 16
 364:     // Length of uniurl string to achive ~119 bits of entropy, closest
 365:     // to what can be losslessly converted to UUIDv4 (122 bits).
 366:     UUIDLen = 20
 367: )
 368:  
 369: // Standard characters allowed in uniuri string.
 370: // 验证码中标准的字符 大小写与数字
 371: var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
 372:  
 373: // New returns a new random string of the standard length, consisting of
 374: // standard characters.
 375: func New() string {
 376:     return NewLenChars(StdLen, StdChars)
 377:  
 378: }
 379:  
 380: // NewLen returns a new random string of the provided length, consisting of standard characters.
 381: // 返回指定长度的随机字符串
 382: func NewLen(length int) string {
 383:     return NewLenChars(length, StdChars)
 384:  
 385: }
 386:  
 387: // NewLenChars returns a new random string of the provided length, consisting
 388: // of the provided byte slice of allowed characters (maximum 256).
 389: // 返回指定长度,指定候选字符的随机字符串(最大256)
 390: func NewLenChars(length int, chars []byte) string {
 391:     b := make([]byte, length)
 392:     r := make([]byte, length+(length/4)) // storage for random bytes. 随机字节的存储空间, 多读几个以免
 393:  
 394:     clen := byte(len(chars))
 395:     maxrb := byte(256 - (256 % len(chars))) // 问题, 为什么要申请这么长的数组? 看下面循环的 continue 条件
 396:  
 397:     i := 0
 398:     for {
 399:         // rand.Read() 和 io.ReadFull(rand.Reader) 的区别?
 400:         // http://www.cnblogs.com/ghj1976/p/3435940.html
 401:         if _, err := io.ReadFull(crand.Reader, r); err != nil {
 402:             panic("error reading from random source: " + err.Error())
 403:         }
 404:         for _, c := range r {
 405:             if c >= maxrb {
 406:                 // Skip this number to avoid modulo bias.
 407:                 // 跳过 maxrb, 以避免麻烦,这也是随机数要多读几个的原因。
 408:                 continue
 409:             }
 410:             b[i] = chars[c%clen]
 411:             i++
 412:             if i == length {
 413:                 return string(b)
 414:             }
 415:         }
 416:     }
 417:     panic("unreachable")
 418:  
 419: }
 420:  
 421: func pic(w http.ResponseWriter, req *http.Request) {
 422:     // 产生验证码byte数组
 423:     d := make([]byte, 4)
 424:     s := NewLen(4)
 425:     d = []byte(s)
 426:  
 427:     // 把验证码变成需要显示的字符串
 428:     ss := ""
 429:     for v := range d {
 430:         d[v] %= 10
 431:         ss += strconv.FormatInt(int64(d[v]), 32)
 432:     }
 433:  
 434:     // 图片流方式输出
 435:     w.Header().Set("Content-Type", "image/png")
 436:     NewImage(d, 100, 40).WriteTo(w)
 437:  
 438:     // 打印出这次使用的验证码
 439:     fmt.Println(ss)
 440: }
 441:  
 442: func index(w http.ResponseWriter, req *http.Request) {
 443:     str := "<meta charset=\"utf-8\"><h3>golang 图片验证码例子</h3><img border=\"1\" src=\"/pic\" alt=\"图片验证码\" onclick=\"this.src=\'/pic\'\" />"
 444:     w.Header().Set("Content-Type", "text/html")
 445:     w.Write([]byte(str))
 446:  
 447: }
 448:  
 449: func main() {
 450:     http.HandleFunc("/pic", pic)
 451:     http.HandleFunc("/", index)
 452:     s := &http.Server{
 453:         Addr:           ":8080",
 454:         ReadTimeout:    30 * time.Second,
 455:         WriteTimeout:   30 * time.Second,
 456:         MaxHeaderBytes: 1 << 20}
 457:     s.ListenAndServe()
 458:  
 459: }