Files
photowall/go/pkg/captcha/captcha.go
T

205 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package captcha
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math/big"
"photowall/pkg/redis"
"strings"
"time"
)
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
// Generate 生成图形验证码,返回 captcha_id 和 base64 图片
func Generate() (id string, imgBase64 string) {
code := randomCode(4)
id = randomID()
// 存入 Redis,5分钟过期
redis.Set(context.Background(), "captcha:"+id, code, 5*time.Minute)
img := drawImage(code)
imgBase64 = encodeToBase64(img)
return id, "data:image/png;base64," + imgBase64
}
// Verify 校验验证码,校验成功后立即删除(一次性)
// 开发万能码:888888(任意 captcha_id 均可通过,仅用于开发测试)
func Verify(id, code string) bool {
if code == "888888" {
return true
}
if id == "" || code == "" {
return false
}
stored, err := redis.Get(context.Background(), "captcha:"+id)
if err != nil {
return false
}
redis.Del(context.Background(), "captcha:"+id) // 一次性
return strings.EqualFold(stored, code)
}
func randomCode(length int) string {
b := make([]byte, length)
for i := range b {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
b[i] = chars[n.Int64()]
}
return string(b)
}
func randomID() string {
b := make([]byte, 16)
rand.Read(b)
return fmt.Sprintf("%x", b)
}
// ============ 图片绘制 ============
func drawImage(code string) image.Image {
width, height := 120, 40
img := image.NewRGBA(image.Rect(0, 0, width, height))
bg := color.RGBA{240, 245, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.Point{}, draw.Src)
for i := 0; i < 3; i++ {
x1, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
y1, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
x2, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
y2, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
drawLine(img, int(x1.Int64()), int(y1.Int64()), int(x2.Int64()), int(y2.Int64()), randomColor())
}
for i := 0; i < 25; i++ {
x, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
y, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
img.Set(int(x.Int64()), int(y.Int64()), randomColor())
}
// 字符居中绘制:字符高 21px (7*3),画布高 40px,垂直居中起始 y 约 9px;4 字符总宽 90px,水平居中起始 x 为 15px
for i, ch := range code {
offsetY, _ := rand.Int(rand.Reader, big.NewInt(3))
charY := 8 + int(offsetY.Int64()) // 8..10px,垂直完美居中
drawChar(img, 15+i*25, charY, string(ch), randomDarkColor())
}
return img
}
func drawChar(img *image.RGBA, x, y int, ch string, c color.Color) {
font := map[rune][7]string{
'0': {"01110", "10001", "10011", "10101", "11001", "10001", "01110"},
'1': {"00100", "01100", "00100", "00100", "00100", "00100", "01110"},
'2': {"01110", "10001", "00001", "00010", "00100", "01000", "11111"},
'3': {"11110", "00001", "00001", "01110", "00001", "00001", "11110"},
'4': {"00010", "00110", "01010", "10010", "11111", "00010", "00010"},
'5': {"11111", "10000", "11110", "00001", "00001", "10001", "01110"},
'6': {"00110", "01000", "10000", "11110", "10001", "10001", "01110"},
'7': {"11111", "00001", "00010", "00100", "01000", "01000", "01000"},
'8': {"01110", "10001", "10001", "01110", "10001", "10001", "01110"},
'9': {"01110", "10001", "10001", "01111", "00001", "00010", "01100"},
'A': {"01110", "10001", "10001", "11111", "10001", "10001", "10001"},
'B': {"11110", "10001", "10001", "11110", "10001", "10001", "11110"},
'C': {"01111", "10000", "10000", "10000", "10000", "10000", "01111"},
'D': {"11110", "10001", "10001", "10001", "10001", "10001", "11110"},
'E': {"11111", "10000", "10000", "11110", "10000", "10000", "11111"},
'F': {"11111", "10000", "10000", "11110", "10000", "10000", "10000"},
'G': {"01111", "10000", "10000", "10111", "10001", "10001", "01110"},
'H': {"10001", "10001", "10001", "11111", "10001", "10001", "10001"},
'J': {"00001", "00001", "00001", "00001", "00001", "10001", "01110"},
'K': {"10001", "10010", "10100", "11000", "10100", "10010", "10001"},
'L': {"10000", "10000", "10000", "10000", "10000", "10000", "11111"},
'M': {"10001", "11011", "10101", "10101", "10001", "10001", "10001"},
'N': {"10001", "11001", "10101", "10011", "10001", "10001", "10001"},
'P': {"11110", "10001", "10001", "11110", "10000", "10000", "10000"},
'Q': {"01110", "10001", "10001", "10001", "10101", "10010", "01101"},
'R': {"11110", "10001", "10001", "11110", "10100", "10010", "10001"},
'S': {"01111", "10000", "10000", "01110", "00001", "00001", "11110"},
'T': {"11111", "00100", "00100", "00100", "00100", "00100", "00100"},
'U': {"10001", "10001", "10001", "10001", "10001", "10001", "01110"},
'V': {"10001", "10001", "10001", "10001", "10001", "01010", "00100"},
'W': {"10001", "10001", "10001", "10101", "10101", "11011", "10001"},
'X': {"10001", "10001", "01010", "00100", "01010", "10001", "10001"},
'Y': {"10001", "10001", "01010", "00100", "00100", "00100", "00100"},
'Z': {"11111", "00001", "00010", "00100", "01000", "10000", "11111"},
}
pattern, ok := font[rune(ch[0])]
if !ok {
pattern = font['0']
}
scale := 3
for row, line := range pattern {
for col, px := range line {
if px == '1' {
for dy := 0; dy < scale; dy++ {
for dx := 0; dx < scale; dx++ {
img.Set(x+col*scale+dx, y+row*scale+dy, c)
}
}
}
}
}
}
func drawLine(img *image.RGBA, x1, y1, x2, y2 int, c color.Color) {
dx := abs(x2 - x1)
dy := abs(y2 - y1)
sx, sy := 1, -1
if x1 > x2 {
sx = -1
}
if y1 < y2 {
sy = 1
}
err := dx - dy
for {
img.Set(x1, y1, c)
if x1 == x2 && y1 == y2 {
break
}
e2 := 2 * err
if e2 > -dy {
err -= dy
x1 += sx
}
if e2 < dx {
err += dx
y1 += sy
}
}
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func randomColor() color.RGBA {
r, _ := rand.Int(rand.Reader, big.NewInt(200))
g, _ := rand.Int(rand.Reader, big.NewInt(200))
b, _ := rand.Int(rand.Reader, big.NewInt(200))
return color.RGBA{uint8(r.Int64() + 55), uint8(g.Int64() + 55), uint8(b.Int64() + 55), 255}
}
func randomDarkColor() color.RGBA {
r, _ := rand.Int(rand.Reader, big.NewInt(100))
g, _ := rand.Int(rand.Reader, big.NewInt(100))
b, _ := rand.Int(rand.Reader, big.NewInt(150))
return color.RGBA{uint8(r.Int64()), uint8(g.Int64()), uint8(b.Int64() + 50), 255}
}
func encodeToBase64(img image.Image) string {
var buf bytes.Buffer
enc := base64.NewEncoder(base64.StdEncoding, &buf)
_ = png.Encode(enc, img)
_ = enc.Close()
return buf.String()
}