172 lines
4.6 KiB
Go
172 lines
4.6 KiB
Go
package service
|
||
|
||
import (
|
||
"carrotskin/pkg/config"
|
||
"carrotskin/pkg/redis"
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"log"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/wenlng/go-captcha-assets/resources/imagesv2"
|
||
"github.com/wenlng/go-captcha-assets/resources/tiles"
|
||
"github.com/wenlng/go-captcha/v2/slide"
|
||
)
|
||
|
||
var (
|
||
slideTileCapt slide.Captcha
|
||
cfg *config.Config
|
||
)
|
||
|
||
// 常量定义(业务相关配置,与Redis连接配置分离)
|
||
const (
|
||
redisKeyPrefix = "captcha:" // Redis键前缀(便于区分业务)
|
||
paddingValue = 3 // 验证允许的误差像素(±3px)
|
||
)
|
||
|
||
// Init 验证码图初始化
|
||
func init() {
|
||
cfg, _ = config.Load()
|
||
// 从默认仓库中获取主图
|
||
builder := slide.NewBuilder()
|
||
bgImage, err := imagesv2.GetImages()
|
||
if err != nil {
|
||
log.Fatalln(err)
|
||
}
|
||
// 滑块形状获取
|
||
graphs := getSlideTileGraphArr()
|
||
|
||
builder.SetResources(
|
||
slide.WithGraphImages(graphs),
|
||
slide.WithBackgrounds(bgImage),
|
||
)
|
||
slideTileCapt = builder.Make()
|
||
if slideTileCapt == nil {
|
||
log.Fatalln("验证码实例初始化失败")
|
||
}
|
||
}
|
||
|
||
// getSlideTileGraphArr 滑块选择
|
||
func getSlideTileGraphArr() []*slide.GraphImage {
|
||
graphs, err := tiles.GetTiles()
|
||
if err != nil {
|
||
log.Fatalln(err)
|
||
}
|
||
var newGraphs = make([]*slide.GraphImage, 0, len(graphs))
|
||
for i := 0; i < len(graphs); i++ {
|
||
graph := graphs[i]
|
||
newGraphs = append(newGraphs, &slide.GraphImage{
|
||
OverlayImage: graph.OverlayImage,
|
||
MaskImage: graph.MaskImage,
|
||
ShadowImage: graph.ShadowImage,
|
||
})
|
||
}
|
||
return newGraphs
|
||
}
|
||
|
||
// RedisData 存储到Redis的验证信息(仅包含校验必需字段)
|
||
type RedisData struct {
|
||
Tx int `json:"tx"` // 滑块目标X坐标
|
||
Ty int `json:"ty"` // 滑块目标Y坐标
|
||
}
|
||
|
||
// GenerateCaptchaData 提取生成验证码的相关信息
|
||
func GenerateCaptchaData(ctx context.Context, redisClient *redis.Client) (string, string, string, int, error) {
|
||
// 生成uuid作为验证码进程唯一标识
|
||
captchaID := uuid.NewString()
|
||
if captchaID == "" {
|
||
return "", "", "", 0, errors.New("生成验证码唯一标识失败")
|
||
}
|
||
|
||
captData, err := slideTileCapt.Generate()
|
||
if err != nil {
|
||
return "", "", "", 0, fmt.Errorf("生成验证码失败: %w", err)
|
||
}
|
||
blockData := captData.GetData()
|
||
if blockData == nil {
|
||
return "", "", "", 0, errors.New("获取验证码数据失败")
|
||
}
|
||
block, _ := json.Marshal(blockData)
|
||
var blockMap map[string]interface{}
|
||
|
||
if err := json.Unmarshal(block, &blockMap); err != nil {
|
||
return "", "", "", 0, fmt.Errorf("反序列化为map失败: %w", err)
|
||
}
|
||
// 提取x和y并转换为int类型
|
||
tx, ok := blockMap["x"].(float64)
|
||
if !ok {
|
||
return "", "", "", 0, errors.New("无法将x转换为float64")
|
||
}
|
||
var x = int(tx)
|
||
ty, ok := blockMap["y"].(float64)
|
||
if !ok {
|
||
return "", "", "", 0, errors.New("无法将y转换为float64")
|
||
}
|
||
var y = int(ty)
|
||
var mBase64, tBase64 string
|
||
mBase64, err = captData.GetMasterImage().ToBase64()
|
||
if err != nil {
|
||
return "", "", "", 0, fmt.Errorf("主图转换为base64失败: %w", err)
|
||
}
|
||
tBase64, err = captData.GetTileImage().ToBase64()
|
||
if err != nil {
|
||
return "", "", "", 0, fmt.Errorf("滑块图转换为base64失败: %w", err)
|
||
}
|
||
redisData := RedisData{
|
||
Tx: x,
|
||
Ty: y,
|
||
}
|
||
redisDataJSON, _ := json.Marshal(redisData)
|
||
redisKey := redisKeyPrefix + captchaID
|
||
expireTime := 300 * time.Second
|
||
|
||
// 使用注入的Redis客户端
|
||
if err := redisClient.Set(
|
||
ctx,
|
||
redisKey,
|
||
redisDataJSON,
|
||
expireTime,
|
||
); err != nil {
|
||
return "", "", "", 0, fmt.Errorf("存储验证码到redis失败: %w", err)
|
||
}
|
||
return mBase64, tBase64, captchaID, y - 10, nil
|
||
}
|
||
|
||
// VerifyCaptchaData 验证用户验证码
|
||
func VerifyCaptchaData(ctx context.Context, redisClient *redis.Client, dx int, id string) (bool, error) {
|
||
// 测试环境下直接通过验证
|
||
cfg, err := config.GetConfig()
|
||
if err == nil && cfg.IsTestEnvironment() {
|
||
return true, nil
|
||
}
|
||
|
||
redisKey := redisKeyPrefix + id
|
||
|
||
// 从Redis获取验证信息,使用注入的客户端
|
||
dataJSON, err := redisClient.Get(ctx, redisKey)
|
||
if err != nil {
|
||
if redisClient.Nil(err) { // 使用封装客户端的Nil错误
|
||
return false, errors.New("验证码已过期或无效")
|
||
}
|
||
return false, fmt.Errorf("redis查询失败: %w", err)
|
||
}
|
||
var redisData RedisData
|
||
if err := json.Unmarshal([]byte(dataJSON), &redisData); err != nil {
|
||
return false, fmt.Errorf("解析redis数据失败: %w", err)
|
||
}
|
||
tx := redisData.Tx
|
||
ty := redisData.Ty
|
||
ok := slide.Validate(dx, ty, tx, ty, paddingValue)
|
||
|
||
// 验证后立即删除Redis记录(防止重复使用)
|
||
if ok {
|
||
if err := redisClient.Del(ctx, redisKey); err != nil {
|
||
// 记录警告但不影响验证结果
|
||
log.Printf("删除验证码Redis记录失败: %v", err)
|
||
}
|
||
}
|
||
return ok, nil
|
||
}
|