Files
backend/internal/service/captcha_service.go
lan 034e02e93a feat: Enhance dependency injection and service integration
- Updated main.go to initialize email service and include it in the dependency injection container.
- Refactored handlers to utilize context in service method calls, improving consistency and error handling.
- Introduced new service options for upload, security, and captcha services, enhancing modularity and testability.
- Removed unused repository implementations to streamline the codebase.

This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
2025-12-02 22:52:33 +08:00

195 lines
4.8 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"
"go.uber.org/zap"
)
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坐标
}
// captchaService CaptchaService的实现
type captchaService struct {
redis *redis.Client
logger *zap.Logger
}
// NewCaptchaService 创建CaptchaService实例
func NewCaptchaService(redisClient *redis.Client, logger *zap.Logger) CaptchaService {
return &captchaService{
redis: redisClient,
logger: logger,
}
}
// Generate 生成验证码
func (s *captchaService) Generate(ctx context.Context) (masterImg, tileImg, captchaID string, y int, err error) {
// 生成uuid作为验证码进程唯一标识
captchaID = uuid.NewString()
if captchaID == "" {
err = errors.New("生成验证码唯一标识失败")
return
}
captData, err := slideTileCapt.Generate()
if err != nil {
err = fmt.Errorf("生成验证码失败: %w", err)
return
}
blockData := captData.GetData()
if blockData == nil {
err = errors.New("获取验证码数据失败")
return
}
block, _ := json.Marshal(blockData)
var blockMap map[string]interface{}
if err = json.Unmarshal(block, &blockMap); err != nil {
err = fmt.Errorf("反序列化为map失败: %w", err)
return
}
// 提取x和y并转换为int类型
tx, ok := blockMap["x"].(float64)
if !ok {
err = errors.New("无法将x转换为float64")
return
}
var x = int(tx)
ty, ok := blockMap["y"].(float64)
if !ok {
err = errors.New("无法将y转换为float64")
return
}
y = int(ty)
masterImg, err = captData.GetMasterImage().ToBase64()
if err != nil {
err = fmt.Errorf("主图转换为base64失败: %w", err)
return
}
tileImg, err = captData.GetTileImage().ToBase64()
if err != nil {
err = fmt.Errorf("滑块图转换为base64失败: %w", err)
return
}
redisData := RedisData{
Tx: x,
Ty: y,
}
redisDataJSON, _ := json.Marshal(redisData)
redisKey := redisKeyPrefix + captchaID
expireTime := 300 * time.Second
// 使用注入的Redis客户端
if err = s.redis.Set(ctx, redisKey, redisDataJSON, expireTime); err != nil {
err = fmt.Errorf("存储验证码到redis失败: %w", err)
return
}
// 返回时 y 需要减10
y = y - 10
return
}
// Verify 验证验证码
func (s *captchaService) Verify(ctx context.Context, dx int, captchaID string) (bool, error) {
// 测试环境下直接通过验证
cfg, err := config.GetConfig()
if err == nil && cfg.IsTestEnvironment() {
return true, nil
}
redisKey := redisKeyPrefix + captchaID
// 从Redis获取验证信息使用注入的客户端
dataJSON, err := s.redis.Get(ctx, redisKey)
if err != nil {
if s.redis.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 := s.redis.Del(ctx, redisKey); err != nil {
// 记录警告但不影响验证结果
s.logger.Warn("删除验证码Redis记录失败", zap.Error(err))
}
}
return ok, nil
}