micro-account/pkg/common/utils/captcha.go
2025-04-14 14:01:13 +08:00

384 lines
11 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 utils
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math/rand"
"net/http"
"os"
"strconv"
"time"
"github.com/nfnt/resize"
)
const (
ImgURL = "https://cdns.fontree.cn/fonchain-main/test/image/artwork/config/slidCode_%d.jpg"
ImgPath = "./code/%d.jpg" //本地路径,暂不使用
)
// 生成指定范围内的随机数
func GetNonceByRange(start, end int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(end-start+1) + start
}
// 获取缓冲图片可能是通过URL或本地路径
func GetBufferedImage(place int) (image.Image, error) {
nonce := GetNonceByRange(1, 10)
var imgURL string
place = 0 // 注意这边的图片现在只使用url图片
if place == 0 {
imgURL = fmt.Sprintf(ImgURL, nonce)
resp, err := http.Get(imgURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
img, _, err := image.Decode(resp.Body)
if err != nil {
return nil, err
}
return img, nil
} else {
file, err := os.Open(fmt.Sprintf(ImgPath, nonce))
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return img, nil
}
}
// 调整图片大小
func ImageResize(bufferedImage image.Image, width, height int) image.Image {
return resize.Resize(uint(width), uint(height), bufferedImage, resize.Lanczos3)
}
// 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
}