当前位置: 首页 > news >正文

go语言,彩色验证码生成,加减法验证,

代码结构

在这里插入图片描述

相关代码

captcha/internal/captcha/generator.go

package captchaimport (_ "embed" // 👈 启用 embed"image""image/color""image/draw""image/png""io""math/rand""golang.org/x/image/font""golang.org/x/image/font/opentype""golang.org/x/image/math/fixed"
)// 👇 嵌入字体文件
//
//go:embed font/font.ttf
var fontBytes []bytevar (white = color.RGBA{255, 255, 255, 255}black = color.RGBA{0, 0, 0, 255}
)func randomColor() color.Color {return color.RGBA{R: uint8(rand.Intn(256)),G: uint8(rand.Intn(256)),B: uint8(rand.Intn(256)),A: 255,}
}// CaptchaImage 生成验证码图片并写入 io.Writer
func CaptchaImage(question string, w io.Writer) error {initRand()const (width  = 150height = 70)img := image.NewRGBA(image.Rect(0, 0, width, height))draw.Draw(img, img.Bounds(), &image.Uniform{white}, image.Point{}, draw.Src)// 画干扰线for i := 0; i < 5; i++ {drawLine(img, rand.Intn(width), rand.Intn(height), rand.Intn(width), rand.Intn(height), randomColor())}// 画噪点for i := 0; i < 50; i++ {img.Set(rand.Intn(width), rand.Intn(height), black)}// 画文字(使用 freetype 渲染)if err := drawTextWithFreetype(img, question, 10, 60, randomColor()); err != nil {return err}return png.Encode(w, img)
}func drawLine(img *image.RGBA, x1, y1, x2, y2 int, c color.Color) {dx := abs(x2 - x1)dy := abs(y2 - y1)var sx, sy intif x1 < x2 {sx = 1} else {sx = -1}if y1 < y2 {sy = 1} else {sy = -1}err := dx - dyfor {img.Set(x1, y1, c)if x1 == x2 && y1 == y2 {break}e2 := 2 * errif e2 > -dy {err -= dyx1 += sx}if e2 < dx {err += dxy1 += sy}}
}func drawTextWithFreetype(img *image.RGBA, text string, x, y int, _ color.Color) error {fontParsed, err := opentype.Parse(fontBytes)if err != nil {return err}face, err := opentype.NewFace(fontParsed, &opentype.FaceOptions{Size:    32,DPI:     72,Hinting: font.HintingNone,})if err != nil {return err}currentX := xfor _, char := range text {// 为每个字符生成随机颜色charColor := randomColor()d := &font.Drawer{Dst:  img,Src:  image.NewUniform(charColor), // 👈 每个字符独立颜色Face: face,Dot:  fixed.P(currentX, y),}d.DrawString(string(char))// 手动计算字符宽度(简单估算,或使用 font.Measure)bounds, _ := font.BoundString(face, string(char))advance := (bounds.Max.X - bounds.Min.X).Ceil()currentX += advance + 2 // +2 为字符间距微调}return nil
}func abs(x int) int {if x < 0 {return -x}return x
}

internal/captcha/logic.go

// internal/captcha/logic.go
package captchaimport ("math/rand""strconv""strings"
)type CaptchaResult struct {Question stringAnswer   intToken    string
}func GenerateCaptcha() *CaptchaResult {initRand()a := rand.Intn(21) // 0-20b := rand.Intn(21)var op stringvar result intif rand.Intn(2) == 0 {op = "+"result = a + b} else {op = "-"if a < b {a, b = b, a}result = a - b}question := strconv.Itoa(a) + " " + op + " " + strconv.Itoa(b) + " = ?"return &CaptchaResult{Question: question,Answer:   result,Token:    randString(32),}
}func randString(n int) string {const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"b := make([]byte, n)for i := range b {b[i] = letters[rand.Intn(len(letters))]}return string(b)
}func ValidateAnswer(token string, userInput string, getAnswer func(string) (string, error)) bool {userInput = strings.TrimSpace(userInput)ansStr, err := getAnswer(token)if err != nil {return false}expected, err := strconv.Atoi(ansStr)if err != nil {return false}given, err := strconv.Atoi(userInput)return err == nil && given == expected
}

internal/captcha/rand.go

// internal/captcha/rand.go
package captchaimport ("math/rand""sync""time"
)var (randInit sync.Once
)func initRand() {randInit.Do(func() {rand.Seed(time.Now().UnixNano())})
}

pkg/redis/redis.go

// pkg/redis/redis.go
package redisimport ("context""time""github.com/go-redis/redis/v8"
)type Client struct {*redis.Client
}func NewClient(addr, password string, db int) *Client {rdb := redis.NewClient(&redis.Options{Addr:     addr,Password: password,DB:       db,})return &Client{rdb}
}func (c *Client) SetCaptcha(ctx context.Context, key string, answer int, expiration time.Duration) error {return c.Set(ctx, key, answer, expiration).Err()
}func (c *Client) GetCaptcha(ctx context.Context, key string) (string, error) {return c.Get(ctx, key).Result()
}func (c *Client) DeleteCaptcha(ctx context.Context, key string) error {return c.Del(ctx, key).Err()
}

main.go

// main.go
package mainimport ("context""fmt""go_collect/captcha/internal/captcha""go_collect/captcha/pkg/redis""log""net/http""time""github.com/gin-gonic/gin"
)var redisClient *redis.Clientfunc init() {// 生产环境应从配置文件或环境变量读取redisClient = redis.NewClient("localhost:6377", "", 0)// 测试连接if err := redisClient.Ping(context.Background()).Err(); err != nil {log.Fatal("❌ Redis 连接失败:", err)}fmt.Println("✅ Redis 连接成功")
}func main() {r := gin.Default()// 获取验证码图片r.GET("/captcha", getCaptchaHandler)// 验证答案r.POST("/captcha/verify", verifyCaptchaHandler)r.Run(":8088")
}func getCaptchaHandler(c *gin.Context) {// 生成逻辑cap := captcha.GenerateCaptcha()// 缓存答案到 Redis,5分钟过期ctx := context.Background()err := redisClient.SetCaptcha(ctx, cap.Token, cap.Answer, 5*time.Minute)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "生成验证码失败"})return}// 设置响应头c.Header("Content-Type", "image/png")c.Header("X-Captcha-Token", cap.Token) // 前端需读取此 Header 或返回 JSON// 生成图片err = captcha.CaptchaImage(cap.Question, c.Writer)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "渲染图片失败"})return}
}type VerifyRequest struct {Token  string `json:"token" binding:"required"`Answer string `json:"answer" binding:"required"`
}func verifyCaptchaHandler(c *gin.Context) {var req VerifyRequestif err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 校验答案isValid := captcha.ValidateAnswer(req.Token, req.Answer, func(token string) (string, error) {ans, err := redisClient.GetCaptcha(context.Background(), token)if err != nil {return "", err}// 验证后删除,防止重复使用redisClient.DeleteCaptcha(context.Background(), token)return ans, nil})if isValid {c.JSON(http.StatusOK, gin.H{"success": true,"message": "验证通过",})} else {c.JSON(http.StatusOK, gin.H{"success": false,"message": "验证失败",})}
}

调用

http://localhost:8088/captcha

在这里插入图片描述

验证

http://localhost:8088/captcha/verify
{"token":"sWmHAreIaA5jC7WqshKHXOjDMTH4I9kV","answer":"7"
}
{"message": "验证通过","success": true
}

在这里插入图片描述


文章转载自:

http://v9wOE6li.hjsrL.cn
http://yqHUk6Ma.hjsrL.cn
http://cPZ3bynp.hjsrL.cn
http://RRk2fuZy.hjsrL.cn
http://j5V3oVAA.hjsrL.cn
http://9KeF3Lws.hjsrL.cn
http://TZbeiAJ5.hjsrL.cn
http://VIKwUGAY.hjsrL.cn
http://tcwOufg9.hjsrL.cn
http://ZguaT5MM.hjsrL.cn
http://JOTTh6zz.hjsrL.cn
http://qgnrKARy.hjsrL.cn
http://2dJIan9c.hjsrL.cn
http://B6PZc8LJ.hjsrL.cn
http://H12xh38D.hjsrL.cn
http://pJqNVnb3.hjsrL.cn
http://PxBRiGII.hjsrL.cn
http://0PR7bbpe.hjsrL.cn
http://zc9YHWPi.hjsrL.cn
http://CYFUuYHy.hjsrL.cn
http://sWbf6ipd.hjsrL.cn
http://DIeS8Ivb.hjsrL.cn
http://HVcYsZae.hjsrL.cn
http://xmgcGI6j.hjsrL.cn
http://wiwzDUhn.hjsrL.cn
http://iow2JeS8.hjsrL.cn
http://lBO2awjc.hjsrL.cn
http://9DMmSGJR.hjsrL.cn
http://x5vvfIqv.hjsrL.cn
http://G7sLckVf.hjsrL.cn
http://www.dtcms.com/a/379627.html

相关文章:

  • 深入解析 AST2600 H2B 接口:架构、原理与完整开发指南
  • 手机ip隔离方法
  • RAG检索增强生成:让AI拥有“外部记忆“的黑科技
  • Jmter接口网站压力测试工具使用记录
  • Agentic BI技术解构:多智能体协作框架如何实现“分析-决策-执行”闭环?
  • 如何用AI做海报、IP设计,稿定AI一站式创作
  • Threejs案例实践笔记
  • React18学习笔记(一) 如何创建一个React项目,JSX的基础应用,案例---视频网站评论区
  • 【Threejs】学习笔记
  • 图像显示技术与色彩转换:从基础原理到实际应用
  • C 语言实现 I.MX6ULL 点灯(续上一篇)、SDK、deep及bsp工程管理
  • 飞桨paddlepaddle旧版本2.4.2安装
  • 2.5 DNS(Domain Name System)
  • CK: 03靶场渗透
  • User类CRUD实现
  • AFSim2.9.0学习笔记 —— 4.2、ArkSIM文件结构介绍及项目结构整理
  • JavaScript WebAPI 指南
  • 计算机毕业设计 基于Hadoop的南昌房价数据分析系统的设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 电路学习(六)三极管
  • 照度传感器考虑笔记
  • 在springboot中使用okhttp3
  • Android开发之Android官方模拟器启动失败问题跟踪排查
  • 第4节-排序和限制-FETCH
  • 2025.9.11总结
  • 三大范式是什么?
  • 传统文化与现代科技的完美融合:文昌帝君灵签系统开发实践
  • 避坑指南:从原理出发解决常见问题
  • 什么是特征冗余度?
  • 数据结构----栈的顺序存储(顺序栈)
  • Java 线上问题排查:从基础到高级的全面指南