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
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user