Files
0451meishi/backend/internal/handlers/captcha.go
2026-01-15 11:37:22 +08:00

81 lines
2.2 KiB
Go

package handlers
import (
"crypto/rand"
"encoding/hex"
"html"
"net/http"
"strings"
"time"
"0451meishiditu/backend/internal/resp"
"github.com/gin-gonic/gin"
)
type captchaNewResp struct {
CaptchaID string `json:"captcha_id"`
SVG string `json:"svg"`
}
// CaptchaNew returns a simple SVG captcha and stores the answer in Redis.
// TTL: 5 minutes. One-time: successful validate will delete it.
func (h *Handlers) CaptchaNew(c *gin.Context) {
code := randomCaptchaCode(5)
id := randomHexStr(16)
key := "captcha:" + id
if err := h.rdb.Set(c.Request.Context(), key, strings.ToLower(code), 5*time.Minute).Err(); err != nil {
resp.Fail(c, http.StatusInternalServerError, "captcha store failed")
return
}
resp.OK(c, captchaNewResp{
CaptchaID: id,
SVG: captchaSVG(code),
})
}
func (h *Handlers) verifyCaptcha(c *gin.Context, captchaID, captchaCode string) bool {
captchaID = strings.TrimSpace(captchaID)
captchaCode = strings.ToLower(strings.TrimSpace(captchaCode))
if captchaID == "" || captchaCode == "" {
return false
}
key := "captcha:" + captchaID
val, err := h.rdb.Get(c.Request.Context(), key).Result()
if err != nil {
return false
}
ok := strings.ToLower(strings.TrimSpace(val)) == captchaCode
if ok {
_ = h.rdb.Del(c.Request.Context(), key).Err()
}
return ok
}
func randomHexStr(n int) string {
b := make([]byte, n)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
func randomCaptchaCode(n int) string {
const alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
b := make([]byte, n)
_, _ = rand.Read(b)
out := make([]byte, n)
for i := 0; i < n; i++ {
out[i] = alphabet[int(b[i])%len(alphabet)]
}
return string(out)
}
func captchaSVG(code string) string {
// Minimal SVG; frontends can render via v-html or <img src="data:image/svg+xml;utf8,...">
esc := html.EscapeString(code)
return `<?xml version="1.0" encoding="UTF-8"?>` +
`<svg xmlns="http://www.w3.org/2000/svg" width="140" height="44" viewBox="0 0 140 44">` +
`<rect x="0" y="0" width="140" height="44" rx="10" fill="#f8fafc" stroke="#e2e8f0"/>` +
`<text x="70" y="29" text-anchor="middle" font-family="ui-sans-serif,system-ui" font-size="22" font-weight="700" fill="#0f172a" letter-spacing="3">` + esc + `</text>` +
`</svg>`
}