169 lines
5.1 KiB
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
|
|
}
|