Files
backend/internal/service/captcha_service.go
lan 4b4980820f
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Test / test (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled
chore: 初始化仓库,排除二进制文件和覆盖率文件
2025-11-28 23:30:49 +08:00

166 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}