Skip to content

CrazyLionCat/captcha

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

captcha

验证码图片生成、答案存储和校验。

特性

  • 算术验证码:例如 8+7=
  • 字符验证码:默认排除 0/1/I/O 等易混字符
  • 输出 PNG bytes、裸 base64、data URL
  • 默认内存存储,开箱即用
  • 对外暴露最小 Storage 接口,可自行扩展 Redis
  • 一次性校验:成功或失败都会消费验证码

安装

go get github.com/CrazyLionCat/captcha

开箱即用

package 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, err

手动保存和校验

store := 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 扩展

模块默认不引入 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,
	})
}

HTTP 示例

这里只演示如何把图片写到自己的接口返回值里,返回结构由你的项目自己定义。

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)
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages