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