Files
photowall/go/pkg/captcha/captcha.go
T
hero920103 57d9866dab feat: PhotoWall 毕业照存储系统初始版本
后端: Go(Gin+GORM+MySQL+Redis)
- 17张业务表(yz_pw_前缀), 自动迁移
- JWT认证(3小时) + 盐+MD5密码 + 图形验证码
- 班级CRUD/加入(8人姓名验证/邀请码)/审核/30天自动清理
- 系统配置(16项)/菜单管理(动态路由)/数据统计
- 文件MD5去重/数据隔离/账号封禁/敏感词DFA检测

前端: Vue3+Vite+Element Plus+Less+ECharts+FontAwesome
- 动态路由(数据库菜单驱动)
- 蓝白配色, H5响应式
- 登录/注册/忘记密码/个人中心
- 班级创建向导/详情/加入/列表
- 管理后台: 审核/配置/菜单/统计/用户/敏感词

数据库: MySQL 10.31.100.3:3306/photowall
管理员: hero920103 / 920103
2026-09-03 05:55:23 +08:00

199 lines
6.2 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 (
"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 < 4; 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 < 30; 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())
}
for i, ch := range code {
drawChar(img, 15+i*25, 28, 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-7*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 strings.Builder
png.Encode(base64.NewEncoder(base64.StdEncoding, &buf), img)
return buf.String()
}