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 }