yintai2/app/admin/service/captcha.go

169 lines
5.1 KiB
Go

package service
import (
"bytes"
"encoding/base64"
"image"
"image/color"
"image/png"
"math/rand/v2"
"strings"
"time"
"yintai-api/app/common/cache"
err2 "yintai-api/app/common/err"
"github.com/smbrave/goutil"
)
const captchaAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
var captchaFont = map[rune][7]byte{
'2': {0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111},
'3': {0b01110, 0b10001, 0b00001, 0b00110, 0b00001, 0b10001, 0b01110},
'4': {0b00100, 0b01000, 0b10000, 0b10010, 0b11111, 0b00010, 0b00010},
'5': {0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110},
'6': {0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110},
'7': {0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000},
'8': {0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110},
'9': {0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100},
'A': {0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001},
'B': {0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110},
'C': {0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110},
'D': {0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110},
'E': {0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111},
'F': {0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000},
'G': {0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01111},
'H': {0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001},
'J': {0b00111, 0b00010, 0b00010, 0b00010, 0b00010, 0b10010, 0b01100},
'K': {0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001},
'L': {0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111},
'M': {0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001},
'N': {0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001},
'P': {0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000},
'Q': {0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101},
'R': {0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001},
'S': {0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110},
'T': {0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100},
'U': {0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110},
'V': {0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100},
'W': {0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b10101, 0b01010},
'X': {0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001},
'Y': {0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100},
'Z': {0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111},
}
func (a *User) Captcha() (string, string) {
code := randomCaptcha(4)
id := goutil.RandomStr(16)
if err := cache.GetRedis().Set(cache.NewKeys().Captcha(id), code, 10*time.Minute).Err(); err != nil {
err2.DbErr("<user captcha>", err)
}
return id, "data:image/png;base64," + base64.StdEncoding.EncodeToString(captchaPNG(code))
}
func verifyCaptcha(id, code string) {
id = strings.TrimSpace(id)
code = strings.TrimSpace(code)
if id == "" {
err2.CaptchaErr()
}
key := cache.NewKeys().Captcha(id)
redix := cache.GetRedis()
saved := redix.Get(key).Val()
redix.Del(key)
if code == "" || saved == "" || !strings.EqualFold(saved, code) {
err2.CaptchaErr()
}
}
func randomCaptcha(n int) string {
buf := make([]byte, n)
for i := range buf {
buf[i] = captchaAlphabet[rand.IntN(len(captchaAlphabet))]
}
return string(buf)
}
func captchaPNG(code string) []byte {
const scale = 3
charW, charH := 5*scale, 7*scale
gap, padX, padY := 10, 14, 12
width := padX*2 + len(code)*charW + (len(code)-1)*gap
height := padY*2 + charH + 8
img := image.NewRGBA(image.Rect(0, 0, width, height))
bg := color.RGBA{245, 246, 248, 255}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
img.Set(x, y, bg)
}
}
for range 8 {
c := color.RGBA{uint8(160 + rand.IntN(70)), uint8(160 + rand.IntN(70)), uint8(160 + rand.IntN(70)), 255}
x1, y1 := rand.IntN(width), rand.IntN(height)
x2, y2 := rand.IntN(width), rand.IntN(height)
drawLine(img, x1, y1, x2, y2, c)
}
for i, r := range code {
glyph, ok := captchaFont[r]
if !ok {
continue
}
ox := padX + i*(charW+gap) + rand.IntN(3) - 1
oy := padY + rand.IntN(5)
ink := color.RGBA{uint8(rand.IntN(80)), uint8(rand.IntN(80)), uint8(rand.IntN(80)), 255}
for row := 0; row < 7; row++ {
bits := glyph[row]
for col := 0; col < 5; col++ {
if bits&(1<<uint(4-col)) == 0 {
continue
}
for dy := 0; dy < scale; dy++ {
for dx := 0; dx < scale; dx++ {
img.Set(ox+col*scale+dx, oy+row*scale+dy, ink)
}
}
}
}
}
var buf bytes.Buffer
_ = png.Encode(&buf, img)
return buf.Bytes()
}
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 {
if image.Pt(x1, y1).In(img.Bounds()) {
img.Set(x1, y1, c)
}
if x1 == x2 && y1 == y2 {
return
}
e2 := 2 * err
if e2 > -dy {
err -= dy
x1 += sx
}
if e2 < dx {
err += dx
y1 += sy
}
}
}
func abs(n int) int {
if n < 0 {
return -n
}
return n
}