micro-account/pkg/common/utils/captcha.go

416 lines
12 KiB
Go
Raw Normal View History

2025-02-21 08:57:10 +00:00
package utils
import (
"bytes"
"encoding/base64"
2025-04-14 04:00:40 +00:00
"errors"
2025-02-21 08:57:10 +00:00
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math/rand"
"net/http"
"os"
"strconv"
"time"
"github.com/nfnt/resize"
)
const (
2025-04-14 04:00:40 +00:00
ImgURL = "https://cdns.fontree.cn/fonchain-main/test/image/artwork/config/slidCode_%d.jpg"
ImgPath = "./code/%d.jpg" //本地路径,暂不使用
ImgPathPng = "./code/%d.png" //本地路径,暂不使用
2025-02-21 08:57:10 +00:00
)
// 生成指定范围内的随机数
func GetNonceByRange(start, end int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(end-start+1) + start
}
// 获取缓冲图片可能是通过URL或本地路径
2025-04-14 04:00:40 +00:00
func GetBufferedImage(place int, canvasWidth, canvasHeight int) (draw.Image, error) {
startTime := time.Now()
fmt.Println("")
fmt.Println("")
fmt.Println("")
fmt.Println("1---")
2025-02-21 08:57:10 +00:00
nonce := GetNonceByRange(1, 10)
var imgURL string
2025-04-14 04:00:40 +00:00
//place = 0 // 注意这边的图片现在只使用url图片
if place == 1 {
2025-02-21 08:57:10 +00:00
2025-04-14 04:00:40 +00:00
fmt.Println("3-1", time.Now().Sub(startTime))
2025-02-21 08:57:10 +00:00
imgURL = fmt.Sprintf(ImgURL, nonce)
resp, err := http.Get(imgURL)
if err != nil {
return nil, err
}
2025-04-14 04:00:40 +00:00
fmt.Println("3-2", time.Now().Sub(startTime))
2025-02-21 08:57:10 +00:00
defer resp.Body.Close()
2025-04-14 04:00:40 +00:00
img, ff, err := image.Decode(resp.Body)
fmt.Println("----", ff)
2025-02-21 08:57:10 +00:00
if err != nil {
return nil, err
}
2025-04-14 04:00:40 +00:00
fmt.Println("3-3", time.Now().Sub(startTime))
canvasImage := ImageResize(img, canvasWidth, canvasHeight).(*image.RGBA)
return canvasImage, nil
} else { //从redis中获取
file, err := os.Open(fmt.Sprintf(ImgPathPng, nonce))
fmt.Println("4-1", time.Now().Sub(startTime))
2025-02-21 08:57:10 +00:00
if err != nil {
return nil, err
}
defer file.Close()
2025-04-14 04:00:40 +00:00
//img, format, err := image.Decode(file)
img, err := png.Decode(file)
format := "1"
fmt.Println("", err)
//img, err := jpeg.Decode(file)
fmt.Println("4-1-1", time.Now().Sub(startTime), err)
fmt.Println("4-2", time.Now().Sub(startTime), format)
2025-02-21 08:57:10 +00:00
if err != nil {
return nil, err
}
2025-04-14 04:00:40 +00:00
ss := ImageResize(img, canvasWidth, canvasHeight)
canvasImage := ss.(*image.RGBA)
return canvasImage, nil
2025-02-21 08:57:10 +00:00
}
2025-04-14 04:00:40 +00:00
return nil, errors.New("类型错误")
2025-02-21 08:57:10 +00:00
}
// 调整图片大小
func ImageResize(bufferedImage image.Image, width, height int) image.Image {
2025-04-14 04:00:40 +00:00
return resize.Resize(uint(width), uint(height), bufferedImage, resize.Bilinear)
2025-02-21 08:57:10 +00:00
}
// CutByTemplate 根据模板裁剪图像 // 生成圆角正方形的滑块图像
func CutByTemplate(canvasImage, blockImage draw.Image, blockWidth, blockHeight, blockX, blockY int) {
// 创建水印图像
waterImage := image.NewRGBA(image.Rect(0, 0, blockWidth, blockHeight))
blockData := getRoundedSquareData(blockWidth, blockHeight)
// 遍历滑块区域,将滑块图案从背景中裁剪出来,并生成水印
for i := 0; i < blockWidth; i++ {
for j := 0; j < blockHeight; j++ {
switch blockData[i][j] {
case 1: // 实心区域
blockImage.Set(i, j, canvasImage.At(blockX+i, blockY+j))
waterImage.Set(i, j, color.Black)
case 2: // 半透明边缘(不再需要)
origColor := canvasImage.At(blockX+i, blockY+j)
r, g, b, _ := origColor.RGBA()
alpha := uint8(180)
blockImage.Set(i, j, color.NRGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), alpha})
waterImage.Set(i, j, color.NRGBA{0, 0, 0, alpha})
case 3: // 纯白边框区域
blockImage.Set(i, j, color.White) // 设置纯白色
waterImage.Set(i, j, color.White)
default: // 透明区域
blockImage.Set(i, j, color.Transparent)
waterImage.Set(i, j, color.Transparent)
}
}
}
// 在原始画布上清除滑块区域并设置透明背景
clearCanvasArea(canvasImage, blockData, blockX, blockY, blockWidth, blockHeight)
}
// 获取带纯白边框的平滑圆角正方形的块数据
func getRoundedSquareData(blockWidth, blockHeight int) [][]int {
data := make([][]int, blockWidth)
for i := range data {
data[i] = make([]int, blockHeight)
}
// 设置圆角半径为正方形边长的 20%
cornerRadius := float64(blockWidth) * 0.2
cornerRadiusSquared := cornerRadius * cornerRadius
// 设置边框厚度为 1 像素
borderThickness := 1.0 // 纯白边框宽度为 1 像素
borderRadiusSquared := (cornerRadius - borderThickness) * (cornerRadius - borderThickness)
for i := 0; i < blockWidth; i++ {
for j := 0; j < blockHeight; j++ {
// 处理四个角的圆角
if (i < int(cornerRadius) && j < int(cornerRadius)) || // 左上角
(i >= blockWidth-int(cornerRadius) && j < int(cornerRadius)) || // 右上角
(i < int(cornerRadius) && j >= blockHeight-int(cornerRadius)) || // 左下角
(i >= blockWidth-int(cornerRadius) && j >= blockHeight-int(cornerRadius)) { // 右下角
// 计算距离的平方,判断是否在圆角内
var dx, dy float64
if i < int(cornerRadius) {
dx = float64(i) - cornerRadius
} else {
dx = float64(i) - float64(blockWidth) + cornerRadius
}
if j < int(cornerRadius) {
dy = float64(j) - cornerRadius
} else {
dy = float64(j) - float64(blockHeight) + cornerRadius
}
distanceSquared := dx*dx + dy*dy
if distanceSquared > cornerRadiusSquared {
data[i][j] = 0 // 圆角外部区域
} else if distanceSquared > borderRadiusSquared {
data[i][j] = 3 // 纯白边框区域
} else {
data[i][j] = 1 // 实心区域
}
} else {
// 非圆角区域
if (i < int(borderThickness) || i >= blockWidth-int(borderThickness)) || // 左右边缘
(j < int(borderThickness) || j >= blockHeight-int(borderThickness)) { // 上下边缘
data[i][j] = 3 // 纯白边框区域
} else {
data[i][j] = 1 // 实心区域
}
}
}
}
return data
}
// 随机生成一个不重叠的滑块区域
func generateNonOverlappingArea(canvasWidth, canvasHeight, blockX, blockY, blockWidth, blockHeight int) (int, int) {
rand.Seed(time.Now().UnixNano())
var newX, newY int
for {
// 随机生成新区域的位置
newX = rand.Intn(canvasWidth - blockWidth)
newY = rand.Intn(canvasHeight - blockHeight)
// 检查新区域是否与原始区域重叠
if (newX+blockWidth <= blockX || newX >= blockX+blockWidth) &&
(newY+blockHeight <= blockY || newY >= blockY+blockHeight) {
break
}
}
return newX, newY
}
// 将滑块区域设为半透明,并生成不重叠的额外区域
func clearCanvasArea(canvasImage draw.Image, blockData [][]int, blockX, blockY, blockWidth, blockHeight int) {
alpha := uint8(128) // 50% 透明度
// 设置原始滑块区域为半透明
for i := 0; i < blockWidth; i++ {
for j := 0; j < blockHeight; j++ {
if blockData[i][j] > 0 {
// 获取原始像素的 RGB 值,并统一设置 Alpha 通道
originalColor := canvasImage.At(blockX+i, blockY+j)
r, g, b, _ := originalColor.RGBA()
// 设置新的颜色并固定透明度
semiTransparentColor := color.NRGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: alpha, // 固定透明度
}
// 应用半透明效果
canvasImage.Set(blockX+i, blockY+j, semiTransparentColor)
}
}
}
// 生成不重叠的新区域位置
canvasBounds := canvasImage.Bounds()
newBlockX, newBlockY := generateNonOverlappingArea(canvasBounds.Max.X, canvasBounds.Max.Y, blockX, blockY, blockWidth, blockHeight)
// 可以在新区域执行与原始区域相同的操作,或根据需求自定义
for i := 0; i < blockWidth; i++ {
for j := 0; j < blockHeight; j++ {
if blockData[i][j] > 0 {
// 获取原始像素的 RGB 值,并统一设置 Alpha 通道
originalColor := canvasImage.At(newBlockX+i, newBlockY+j)
r, g, b, _ := originalColor.RGBA()
// 设置新的颜色并固定透明度
semiTransparentColor := color.NRGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: alpha, // 固定透明度
}
// 应用半透明效果
canvasImage.Set(newBlockX+i, newBlockY+j, semiTransparentColor)
}
}
}
}
//// 清除画布上的滑块区域,使其为黑色背景
//func clearCanvasArea(canvasImage draw.Image, blockData [][]int, blockX, blockY, blockWidth, blockHeight int) {
// alpha := uint8(128) // 50% 透明度
//
// for i := 0; i < blockWidth; i++ {
// for j := 0; j < blockHeight; j++ {
// if blockData[i][j] > 0 {
// // 获取原始像素的 RGB 值,并统一设置 Alpha 通道
// originalColor := canvasImage.At(blockX+i, blockY+j)
// r, g, b, _ := originalColor.RGBA()
//
// // 设置新的颜色并固定透明度
// semiTransparentColor := color.NRGBA{
// R: uint8(r >> 8),
// G: uint8(g >> 8),
// B: uint8(b >> 8),
// A: alpha, // 固定透明度
// }
//
// // 应用半透明效果
// canvasImage.Set(blockX+i, blockY+j, semiTransparentColor)
// }
// }
// }
//}
//// 根据模板裁剪图像 // 生成拼图类型的滑块图像 有需要打开
//func CutByTemplate(canvasImage, blockImage draw.Image, blockWidth, blockHeight, blockRadius, blockX, blockY int) (faceY int) {
// waterImage := image.NewRGBA(image.Rect(0, 0, blockWidth, blockHeight))
// var blockData [][]int
// blockData, faceY = getBlockData(blockWidth, blockHeight, blockRadius, blockY)
//
// for i := 0; i < blockWidth; i++ {
// for j := 0; j < blockHeight; j++ {
// if blockData[i][j] == 1 {
// // 设置水印背景为黑色
// waterImage.Set(i, j, color.Black)
// // 从原始图像中获取像素
// blockImage.Set(i, j, canvasImage.At(blockX+i, blockY+j))
//
// // 设置轮廓为白色
// if (i+1 < blockWidth && blockData[i+1][j] == 0) ||
// (j+1 < blockHeight && blockData[i][j+1] == 0) ||
// (i-1 >= 0 && blockData[i-1][j] == 0) ||
// (j-1 >= 0 && blockData[i][j-1] == 0) {
// blockImage.Set(i, j, color.White)
// waterImage.Set(i, j, color.White)
// }
// } else {
// // 背景设为透明
// blockImage.Set(i, j, color.Transparent)
// waterImage.Set(i, j, color.Transparent)
// }
// }
// }
// addBlockWatermark(canvasImage, waterImage, blockX, blockY)
// return
//}
//
//// 获取块数据,用以确定块的形状
//func getBlockData(blockWidth, blockHeight, blockRadius, blockY int) (blockData [][]int, faceY int) {
// data := make([][]int, blockWidth)
// for i := range data {
// data[i] = make([]int, blockHeight)
// }
// rand.Seed(time.Now().UnixNano())
//
// // 确定两个随机方向和凸/凹形状
// face1, face2 := rand.Intn(4), rand.Intn(4)
// for face1 == face2 {
// face2 = rand.Intn(4)
// }
//
// shape := rand.Intn(2) // 0: 凸1: 凹
// circle1 := getCircleCoords(face1, blockWidth, blockHeight, blockRadius)
// circle2 := getCircleCoords(face2, blockWidth, blockHeight, blockRadius)
//
// if (face1 == 2 || face2 == 2) && shape == 1 {
// faceY = blockY - blockRadius
// } else {
// faceY = blockY
// }
// // 计算轮廓
// radiusSquared := float64(blockRadius * blockRadius)
// for i := 0; i < blockWidth; i++ {
// for j := 0; j < blockHeight; j++ {
// if i >= blockRadius && i < blockWidth-blockRadius && j >= blockRadius && j < blockHeight-blockRadius {
// data[i][j] = 1
// }
// if circle1 != nil && math.Pow(float64(i-circle1[0]), 2)+math.Pow(float64(j-circle1[1]), 2) <= radiusSquared {
// data[i][j] = shape
// }
// if circle2 != nil && math.Pow(float64(i-circle2[0]), 2)+math.Pow(float64(j-circle2[1]), 2) <= radiusSquared {
// data[i][j] = shape
// }
// }
// }
// return data, faceY
//}
//
//// 根据方向获取圆心坐标
//func getCircleCoords(face, blockWidth, blockHeight, blockRadius int) []int {
// switch face {
// case 0:
// return []int{blockWidth / 2, blockRadius} // 上
// case 1:
// return []int{blockRadius, blockHeight / 2} // 左
// case 2:
// return []int{blockWidth / 2, blockHeight - blockRadius - 1} // 下
// case 3:
// return []int{blockWidth - blockRadius - 1, blockHeight / 2} // 右
// default:
// return nil
// }
//}
//
//// 在画布图像上添加水印
//func addBlockWatermark(canvasImage, waterImage draw.Image, blockX, blockY int) {
// for i := 0; i < waterImage.Bounds().Dx(); i++ {
// for j := 0; j < waterImage.Bounds().Dy(); j++ {
// if _, _, _, alpha := waterImage.At(i, j).RGBA(); alpha > 0 {
// canvasImage.Set(blockX+i, blockY+j, waterImage.At(i, j))
// }
// }
// }
//}
// 将图像转换为Base64编码
func ToBase64(img image.Image, format string) string {
buf := new(bytes.Buffer)
switch format {
case "png":
png.Encode(buf, img)
}
return base64.StdEncoding.EncodeToString(buf.Bytes())
}
// 计算绝对值
func Abs(a int) int {
if a < 0 {
return -a
}
return a
}
// 将字符串转换为整数
func ParseInt(s string) int {
i, _ := strconv.Atoi(s)
return i
}