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
英文地址,請注明出處:http://en.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;設為 …