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://www.pswp.cn/news/922419.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922419.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PuTTY软件访问ZYNQ板卡的Linux系统

PuTTY 是一款非常经典、轻量级、免费的 SSH、Telnet 和串行端口连接客户端&#xff0c;主要运行于 Windows 平台。它是在开源许可下开发的&#xff0c;因其小巧、简单、可靠而成为系统管理员、网络工程师和开发人员的必备工具。网上有非常多的下载资源。 我们使用PuTTY软件对ZY…

做一个RBAC权限

在分布式应用场景下&#xff0c;我们可以利用网关对请求进行集中处理&#xff0c;实现了低耦合&#xff0c;高内聚的特性。 登陆权限验证和鉴权的功能都可以在网关层面进行处理&#xff1a; 用户登录后签署的jwt保存在header中&#xff0c;用户信息则保存在redis中网关应该对不…

【算法】day1 双指针

1、移动零&#xff08;同向分3区域&#xff09; 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a;注意原地操作。快排也是这个方法&#xff1a;左边小于等于 tmp&#xff0c;右边大于 tmp&#xff0c;最后 tmp 放到 dest。 代码&#…

Linux 日志分析:用 ELK 搭建个人运维监控平台

Linux 日志分析&#xff1a;用 ELK 搭建个人运维监控平台 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我放飞…

Linux网络:socket编程UDP

文章目录前言一&#xff0c;socket二&#xff0c;服务端socket3-1 创建socket3-2 绑定地址和端口3-3 接收数据3-4 回复数据3-5关闭socket3-6 完整代码三&#xff0c;客户端socket3-1 为什么客户端通常不需要手动定义 IP 和端口前言 学习 socket 编程的意义在于&#xff1a;它让…

【从零到公网】本地电脑部署服务并实现公网访问(IPv4/IPv6/DDNS 全攻略)

从零到公网&#xff1a;本地电脑部署服务并实现公网访问&#xff08;IPv4/IPv6/DDNS 全攻略&#xff09; 适用场景&#xff1a;本地 API 服务、大模型推理服务、NAS、远程桌面等需要公网访问的场景 关键词&#xff1a;公网 IP、端口映射、内网穿透、IPv6、Cloudflare DDNS 一、…

模块二 落地微服务

11 | 服务发布和引用的实践 服务发布和引用常见的三种方式&#xff1a;Restful API、XML配置以及IDL文件。今天我将以XML配置方式为例&#xff0c;给你讲解服务发布和引用的具体实践以及可能会遇到的问题。 XML配置方式的服务发布和引用流程 1. 服务提供者定义接口 服务提供者发…

C++程序员速通C#:从Hello World到数据类型

C程序员光速入门C#&#xff08;一&#xff09;&#xff1a;总览、数据类型、运算符 一.Hello world&#xff01; 随着.NET的深入人心,作为一个程序员&#xff0c;当然不能在新技术面前停而止步&#xff0c;面对着c在.net中的失败,虽然有一丝遗憾&#xff0c;但是我们应该认识到…

Linux相关概念和易错知识点(44)(IP地址、子网和公网、NAPT、代理)

目录1.IP地址&#xff08;1&#xff09;局域网和公网①局域网a.网关地址b.局域网通信②运营商子网③公网&#xff08;2&#xff09;NAPT①NAPT过程②理解NAPT③理解源IP和目的IPa.目的IPb.源IP③最长前缀匹配④NAT技术缺陷2.代理服务&#xff08;1&#xff09;正向代理&#xf…

工业智能终端赋能自动化生产线建设数字化管理

在当今数字化浪潮的推动下&#xff0c;自动化生产线正逐渐成为各行各业提升效率和降低成本的重要选择。随着智能制造的深入发展&#xff0c;工业智能终端的引入不仅为生产线带来了技术革新&#xff0c;也赋予了数字化管理新的动力。一、工业智能终端&#xff1a;一体化设计&…

【Vue2手录06】计算属性Computed

一、表单元素的v-model绑定&#xff08;核心场景&#xff09; v-model 是Vue实现“表单元素与数据双向同步”的语法糖&#xff0c;不同表单元素的绑定规则存在差异&#xff0c;需根据元素类型选择正确的绑定方式。 1.1 四大表单元素的绑定规则对比表单元素类型绑定数据类型核心…

FPGA入门-数码管静态显示

19. 数码管的静态显示 在许多项目设计中&#xff0c;我们通常需要一些显示设备来显示我们需要的信息&#xff0c;可以选择的显示设备有很多&#xff0c;而数码管是使用最多&#xff0c;最简单的显示设备之一。数码管是一种半导体发光器件&#xff0c;具有响应时间短、体积小、…

深入理解大语言模型(5)-关于token

到目前为止对 LLM 的描述中&#xff0c;我们将其描述为一次预测一个单词&#xff0c;但实际上还有一个更重要的技术细 节。即 LLM 实际上并不是重复预测下一个单词&#xff0c;而是重复预测下一个 token 。对于一个句子&#xff0c;语言模型会 先使用分词器将其拆分为一个个 to…

视觉智能的「破壁者」——Transformer如何重塑计算机视觉范式?三大CV算法论文介绍 ViTMAESwin Transformer

当自然语言处理领域因Transformer而焕发新生时&#xff0c;计算机视觉却长期困于卷积神经网络的架构桎梏。直到ViT&#xff08;Vision Transformer&#xff09;的横空出世&#xff0c;才真正打破了视觉与语言之间的壁垒。它不仅是技术的革新&#xff0c;更是范式革命的开始&…

Java 并发容器源码解析:ConcurrentSkipListSet 行级深度剖析

Java 并发容器源码解析&#xff1a;ConcurrentSkipListSet 行级深度剖析 本文将深入解析 Java 并发容器 ConcurrentSkipListSet 的核心源码&#xff0c;结合流程图、代码注释、设计思想、优缺点分析、业务场景、调试与优化、集成方案、高阶应用等&#xff0c;帮助你系统掌握这款…

答题卡自动识别案例

目录 1.答题卡自动批阅整体实现思路 2.关键技术步骤与原理 答题卡区域提取 ①轮廓检测并排序 ②执行透视变换 ③找到每一个圆圈轮廓 ④先对所有圆圈轮廓从上到下排序 ⑤再通过循环每次只提取出五个轮廓再进行从左到右的排序 3.完整代码 1.答题卡自动批阅整体实现思路 …

C#实现通过POST实现读取数据

C# POST请求与MySQL数据存储实现下面是一个完整的C#解决方案&#xff0c;用于发送POST请求、接收响应数据&#xff0c;并将数据保存到MySQL数据库中。完整代码实现 using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.J…

Java 字符编码问题,怎么优雅地解决?

网罗开发&#xff08;小红书、快手、视频号同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

STL之string类(C++)

1.string类核心定位std::string 本质是对 “字符序列” 的封装&#xff0c;内部通过动态数组存储字符&#xff0c;并自动管理内存&#xff08;分配、扩容、释放&#xff09;&#xff0c;对外提供了简洁的接口用于字符串的创建、修改、拼接、查找等操作。1.1 使用前提头文件包含…

[Maven 基础课程]第一个 Maven 项目

idea 新建一个项目&#xff1a; 来到 New Project 页面&#xff1a; 这里我们有两种方式创建 maven 项目&#xff0c;一种是自定义创建&#xff0c;另一种是使用 maven 模版项目创建。 自定义创建 maven 项目 基本配置 Name: first_maven_project 项目名称&#xff0c;设为 …