Files
x_admin/server/util/aj-captcha-go/service/captcha_service_block_puzzle.go
2025-07-26 00:56:50 +08:00

236 lines
6.7 KiB
Go

package service
import (
"encoding/json"
"errors"
"fmt"
"log"
"math"
"x_admin/util/aj-captcha-go/constant"
"x_admin/util/aj-captcha-go/model/vo"
"x_admin/util/aj-captcha-go/util"
img "x_admin/util/aj-captcha-go/util/image"
"golang.org/x/image/colornames"
)
func NewBlockPuzzleCaptchaService(factory *CaptchaServiceFactory) *BlockPuzzleCaptchaService {
// 初始化静态资源
img.SetUp()
return &BlockPuzzleCaptchaService{
factory: factory,
}
}
type BlockPuzzleCaptchaService struct {
point vo.PointVO
factory *CaptchaServiceFactory
}
// Get 获取验证码图片信息
func (b *BlockPuzzleCaptchaService) Get() (map[string]interface{}, error) {
// 初始化背景图片
backgroundImage := img.GetBackgroundImage()
// 为背景图片设置水印
// backgroundImage.SetText(b.factory.config.Watermark.Text, b.factory.config.Watermark.FontSize, b.factory.config.Watermark.Color)
// 初始化模板图片
templateImage := img.GetTemplateImage()
// 构造前端所需图片
b.pictureTemplatesCut(backgroundImage, templateImage)
originalImageBase64, err := backgroundImage.Base64()
if err != nil {
return nil, err
}
jigsawImageBase64, err := templateImage.Base64()
if err != nil {
return nil, err
}
data := make(map[string]interface{})
data["originalImageBase64"] = originalImageBase64
data["jigsawImageBase64"] = jigsawImageBase64
data["secretKey"] = b.point.SecretKey
data["token"] = util.GetUuid()
codeKey := fmt.Sprintf(constant.CodeKeyPrefix, data["token"])
jsonPoint, err := json.Marshal(b.point)
if err != nil {
log.Printf("point json Marshal err: %v", err)
return nil, err
}
b.factory.GetCache().Set(codeKey, string(jsonPoint), b.factory.config.CacheExpireSec)
return data, nil
}
func (b *BlockPuzzleCaptchaService) pictureTemplatesCut(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil) {
// 生成拼图坐标点
b.generateJigsawPoint(backgroundImage, templateImage)
// 裁剪模板图
b.cutByTemplate(backgroundImage, templateImage, b.point.X, 0)
// 插入干扰图
for {
newTemplateImage := img.GetTemplateImage()
if newTemplateImage.Src != templateImage.Src {
offsetX := util.RandomInt(0, backgroundImage.Width-newTemplateImage.Width-5)
if math.Abs(float64(newTemplateImage.Width-offsetX)) > float64(newTemplateImage.Width/2) {
b.interferenceByTemplate(backgroundImage, newTemplateImage, offsetX, b.point.Y)
break
}
}
}
}
// 插入干扰图
func (b *BlockPuzzleCaptchaService) interferenceByTemplate(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil, x1 int, y1 int) {
xLength := templateImage.Width
yLength := templateImage.Height
for x := 0; x < xLength; x++ {
for y := 0; y < yLength; y++ {
// 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
isOpacity := templateImage.IsOpacity(x, y)
// 当前模板像素在背景图中的位置
backgroundX := x + x1
backgroundY := y + y1
// 当不为透明时
if !isOpacity {
// 背景图区域模糊
backgroundImage.VagueImage(backgroundX, backgroundY)
}
//防止数组越界判断
if x == (xLength-1) || y == (yLength-1) {
continue
}
rightOpacity := templateImage.IsOpacity(x+1, y)
downOpacity := templateImage.IsOpacity(x, y+1)
//描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
if (isOpacity && !rightOpacity) || (!isOpacity && rightOpacity) || (isOpacity && !downOpacity) || (!isOpacity && downOpacity) {
backgroundImage.RgbaImage.SetRGBA(backgroundX, backgroundY, colornames.White)
}
}
}
}
func (b *BlockPuzzleCaptchaService) cutByTemplate(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil, x1, y1 int) {
xLength := templateImage.Width
yLength := templateImage.Height
for x := 0; x < xLength; x++ {
for y := 0; y < yLength; y++ {
// 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
isOpacity := templateImage.IsOpacity(x, y)
// 当前模板像素在背景图中的位置
backgroundX := x + x1
backgroundY := y + y1
// 当不为透明时
if !isOpacity {
// 获取原图像素
backgroundRgba := backgroundImage.RgbaImage.RGBAAt(backgroundX, backgroundY)
// 将原图的像素扣到模板图上
templateImage.SetPixel(backgroundRgba, x, y)
// 背景图区域模糊
backgroundImage.VagueImage(backgroundX, backgroundY)
}
//防止数组越界判断
if x == (xLength-1) || y == (yLength-1) {
continue
}
rightOpacity := templateImage.IsOpacity(x+1, y)
downOpacity := templateImage.IsOpacity(x, y+1)
//描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
if (isOpacity && !rightOpacity) || (!isOpacity && rightOpacity) || (isOpacity && !downOpacity) || (!isOpacity && downOpacity) {
templateImage.RgbaImage.SetRGBA(x, y, colornames.White)
backgroundImage.RgbaImage.SetRGBA(backgroundX, backgroundY, colornames.White)
}
}
}
}
// 生成模板图在背景图中的随机坐标点
func (b *BlockPuzzleCaptchaService) generateJigsawPoint(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil) {
widthDifference := backgroundImage.Width - templateImage.Width
heightDifference := backgroundImage.Height - templateImage.Height
x, y := 0, 0
if widthDifference <= 0 {
x = 5
} else {
x = util.RandomInt(100, widthDifference-100)
}
if heightDifference <= 0 {
y = 5
} else {
y = util.RandomInt(5, heightDifference)
}
point := vo.PointVO{X: x, Y: y}
point.SetSecretKey(util.RandString(16))
b.point = point
}
func (b *BlockPuzzleCaptchaService) Check(token string, pointJson string) error {
cache := b.factory.GetCache()
codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token)
cachePointInfo := cache.Get(codeKey)
if cachePointInfo == "" {
return errors.New("验证码已失效")
}
// 解析结构体
cachePoint := &vo.PointVO{}
userPoint := &vo.PointVO{}
err := json.Unmarshal([]byte(cachePointInfo), cachePoint)
if err != nil {
return err
}
// 解密前端传递过来的数据
userPointJson := util.AesDecrypt(pointJson, cachePoint.SecretKey)
err = json.Unmarshal([]byte(userPointJson), userPoint)
if err != nil {
return err
}
// 校验两个点是否符合
if math.Abs(float64(cachePoint.X-userPoint.X)) <= float64(b.factory.config.BlockPuzzle.Offset) && cachePoint.Y == userPoint.Y {
return nil
}
return errors.New("验证失败")
}
func (b *BlockPuzzleCaptchaService) Verification(token string, pointJson string) error {
err := b.Check(token, pointJson)
if err != nil {
return err
}
codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token)
b.factory.GetCache().Delete(codeKey)
return nil
}