zoukankan      html  css  js  c++  java
  • 验证码代码解读

    下面解读的验证码代码来自: 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: }
  • 相关阅读:
    浏览器渲染HTML页面步骤
    JavaScript中必记英语单词及含义
    JavaScript中的线程与进程
    成绩转换 题解
    计算球的体积 题解 #define
    计算两点间的距离 题解
    ASCII码排序 题解
    python学习——协程
    python学习——进程
    python学习——锁
  • 原文地址:https://www.cnblogs.com/ghj1976/p/3437851.html
Copyright © 2011-2022 走看看