验证码图片生成、答案存储和校验。
- 算术验证码:例如
8+7= - 字符验证码:默认排除
0/1/I/O等易混字符 - 输出 PNG bytes、裸 base64、data URL
- 默认内存存储,开箱即用
- 对外暴露最小
Storage接口,可自行扩展 Redis - 一次性校验:成功或失败都会消费验证码
go get github.com/CrazyLionCat/captchapackage main
import (
"context"
"fmt"
"github.com/CrazyLionCat/captcha"
)
func main() {
service := captcha.NewMemoryService(captcha.Options{})
img, err := service.Generate(context.Background())
if err != nil {
panic(err)
}
// img.ID 用于后续校验,img.Base64 是裸 base64 PNG。
fmt.Println(img.ID)
fmt.Println(img.Base64)
if err := service.VerifyOnce(context.Background(), img.ID, "用户输入"); err != nil {
fmt.Println("验证码错误或已失效:", err)
}
}返回值只包含图片验证码必要字段:
type CaptchaImage struct {
ID string
Base64 string
}前端使用:
<img src="data:image/png;base64,{{base64}}" alt="captcha">service := captcha.NewMemoryService(captcha.Options{
Config: captcha.Config{
Enabled: true,
Type: captcha.TypeChar, // captcha.TypeMath 或 captcha.TypeChar
CharLength: 5,
},
Image: captcha.ImageOptions{
Width: 160,
Height: 60,
},
TTL: 2 * time.Minute,
KeyPrefix: "global:captcha_codes:",
})关闭验证码:
service := captcha.NewMemoryService(captcha.Options{
Disabled: true,
})如果你想自己保存答案,可以直接生成 challenge 和图片。
challenge, img, err := captcha.GeneratePNGBase64(
captcha.DefaultConfig(),
captcha.ImageOptions{},
)
if err != nil {
panic(err)
}
// challenge.Answer 只能保存在服务端。
fmt.Println(challenge.Code)
fmt.Println(challenge.Answer)
fmt.Println(img)生成 data URL:
challenge, dataURL, err := captcha.GeneratePNGDataURL(
captcha.DefaultConfig(),
captcha.ImageOptions{},
)
_, _, _ = challenge, dataURL, err生成 PNG bytes:
challenge := captcha.Generate(captcha.DefaultConfig())
pngBytes, err := captcha.RenderPNG(challenge, captcha.ImageOptions{})
_, _ = pngBytes, errstore := captcha.NewMemoryStore()
challenge := captcha.Generate(captcha.DefaultConfig())
err := captcha.SaveChallenge(context.Background(), store, "uuid", challenge, 2*time.Minute)
if err != nil {
panic(err)
}
err = captcha.VerifyOnce(context.Background(), store, "uuid", "用户输入")只比较答案:
err := captcha.VerifyAnswer("AbC", "abc") // 大小写不敏感模块默认不引入 Redis 依赖。你只需要实现这个接口:
type Storage interface {
Set(ctx context.Context, key string, value string, ttl time.Duration) error
Get(ctx context.Context, key string) (string, error)
Delete(ctx context.Context, key string) error
}github.com/redis/go-redis/v9 适配示例:
package yourpkg
import (
"context"
"time"
"github.com/CrazyLionCat/captcha"
"github.com/redis/go-redis/v9"
)
type RedisStore struct {
client *redis.Client
}
func NewRedisStore(client *redis.Client) *RedisStore {
return &RedisStore{client: client}
}
func (s *RedisStore) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
return s.client.Set(ctx, key, value, ttl).Err()
}
func (s *RedisStore) Get(ctx context.Context, key string) (string, error) {
value, err := s.client.Get(ctx, key).Result()
if err != nil {
return "", err
}
return value, nil
}
func (s *RedisStore) Delete(ctx context.Context, key string) error {
return s.client.Del(ctx, key).Err()
}
func NewCaptchaService(client *redis.Client) *captcha.Service {
return captcha.New(NewRedisStore(client), captcha.Options{
Config: captcha.DefaultConfig(),
TTL: 2 * time.Minute,
})
}这里只演示如何把图片写到自己的接口返回值里,返回结构由你的项目自己定义。
func GetCaptcha(w http.ResponseWriter, r *http.Request) {
img, err := service.Generate(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]string{
"id": img.ID,
"base64": img.Base64,
})
}
func CheckCaptcha(w http.ResponseWriter, r *http.Request) {
id := r.FormValue("id")
code := r.FormValue("code")
if err := service.VerifyOnce(r.Context(), id, code); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
}