340 lines
8.2 KiB
Go
340 lines
8.2 KiB
Go
package captcha
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"github.com/golang/freetype"
|
||
"github.com/llgcode/draw2d"
|
||
"github.com/llgcode/draw2d/draw2dimg"
|
||
"image"
|
||
"image/color"
|
||
"log"
|
||
"math"
|
||
"math/big"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
)
|
||
|
||
const (
|
||
chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
operator = "+-*/"
|
||
defaultLen = 4
|
||
defaultFontSize = 20
|
||
defaultDpi = 72
|
||
)
|
||
|
||
// Captcha 图形验证码 使用字体默认ttf格式
|
||
// w 图片宽度, h图片高度,CodeLen验证码的个数
|
||
// FontSize 字体大小, Dpi 清晰度,FontPath 字体目录, FontName 字体名字
|
||
// mode 验证模式 0:普通字符串,1:10以内简单数学公式
|
||
type Captcha struct {
|
||
W, H, CodeLen int
|
||
FontSize float64
|
||
Dpi int
|
||
FontPath, FontName string
|
||
mode int
|
||
}
|
||
|
||
// NewCaptcha 实例化验证码
|
||
func NewCaptcha(w, h, CodeLen int) *Captcha {
|
||
return &Captcha{W: w, H: h, CodeLen: CodeLen}
|
||
}
|
||
|
||
// OutPut 输出
|
||
func (captcha *Captcha) OutPut() (string, *image.RGBA) {
|
||
img := captcha.initCanvas()
|
||
return captcha.doImage(img)
|
||
}
|
||
|
||
// RangeRand 获取区间[-m, n]的随机数
|
||
func (captcha *Captcha) RangeRand(min, max int64) int64 {
|
||
if min > max {
|
||
panic("the min is greater than max!")
|
||
}
|
||
|
||
if min < 0 {
|
||
f64Min := math.Abs(float64(min))
|
||
i64Min := int64(f64Min)
|
||
result, _ := rand.Int(rand.Reader, big.NewInt(max+1+i64Min))
|
||
|
||
return result.Int64() - i64Min
|
||
} else {
|
||
result, _ := rand.Int(rand.Reader, big.NewInt(max-min+1))
|
||
|
||
return min + result.Int64()
|
||
}
|
||
}
|
||
|
||
// 随机字符串
|
||
func (captcha *Captcha) getRandCode() string {
|
||
if captcha.CodeLen <= 0 {
|
||
captcha.CodeLen = defaultLen
|
||
}
|
||
|
||
var code = ""
|
||
for l := 0; l < captcha.CodeLen; l++ {
|
||
charsPos := captcha.RangeRand(0, int64(len(chars)-1))
|
||
code += string(chars[charsPos])
|
||
}
|
||
|
||
return code
|
||
}
|
||
|
||
// 获取算术运算公式
|
||
func (captcha *Captcha) getFormulaMixData() (string, []string) {
|
||
num1 := int(captcha.RangeRand(11, 20))
|
||
num2 := int(captcha.RangeRand(1, 10))
|
||
opArr := []rune(operator)
|
||
opRand := opArr[captcha.RangeRand(0, 3)]
|
||
|
||
strNum1 := strconv.Itoa(num1)
|
||
strNum2 := strconv.Itoa(num2)
|
||
|
||
var ret int
|
||
var opRet string
|
||
switch string(opRand) {
|
||
case "+":
|
||
ret = num1 + num2
|
||
opRet = "+"
|
||
case "-":
|
||
if num1-num2 > 0 {
|
||
ret = num1 - num2
|
||
opRet = "-"
|
||
} else {
|
||
ret = num1 + num2
|
||
opRet = "+"
|
||
}
|
||
|
||
case "*":
|
||
if num1*num2 < 100 {
|
||
ret = num1 * num2
|
||
//opRet = "×"
|
||
opRet = "×"
|
||
} else {
|
||
ret = num1 + num2
|
||
opRet = "+"
|
||
}
|
||
case "/":
|
||
if num1%num2 == 0 {
|
||
ret = num1 / num2
|
||
opRet = "÷"
|
||
} else {
|
||
ret = num1 + num2
|
||
opRet = "+"
|
||
}
|
||
|
||
}
|
||
|
||
return strconv.Itoa(ret), []string{strNum1, opRet, strNum2, "=", "?"}
|
||
}
|
||
|
||
// 初始化画布
|
||
func (captcha *Captcha) initCanvas() *image.RGBA {
|
||
dest := image.NewRGBA(image.Rect(0, 0, captcha.W, captcha.H))
|
||
// 随机色
|
||
r := uint8(255) // uint8(captcha.RangeRand(50, 250))
|
||
g := uint8(255) // uint8(captcha.RangeRand(50, 250))
|
||
b := uint8(255) // uint8(captcha.RangeRand(50, 250))
|
||
// 填充背景色
|
||
for x := 0; x < captcha.W; x++ {
|
||
for y := 0; y < captcha.H; y++ {
|
||
dest.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255}) //设定alpha图片的透明度
|
||
}
|
||
}
|
||
return dest
|
||
}
|
||
|
||
// 处理图像
|
||
func (captcha *Captcha) doImage(dest *image.RGBA) (string, *image.RGBA) {
|
||
gc := draw2dimg.NewGraphicContext(dest)
|
||
defer gc.Close()
|
||
defer gc.FillStroke()
|
||
captcha.setFont(gc)
|
||
captcha.doPoint(gc)
|
||
captcha.doLine(gc)
|
||
captcha.doSinLine(gc)
|
||
|
||
var codeStr string
|
||
if captcha.mode == 1 {
|
||
ret, formula := captcha.getFormulaMixData()
|
||
// log.Println("算数验证码: ", formula)
|
||
codeStr = ret
|
||
captcha.doFormula(gc, formula)
|
||
} else {
|
||
codeStr = captcha.getRandCode()
|
||
captcha.doCode(gc, codeStr)
|
||
}
|
||
|
||
return codeStr, dest
|
||
}
|
||
|
||
// 验证码字符设置到图像上
|
||
func (captcha *Captcha) doCode(gc *draw2dimg.GraphicContext, code string) {
|
||
for l := 0; l < len(code); l++ {
|
||
y := captcha.RangeRand(int64(captcha.FontSize)-1, int64(captcha.H)+6)
|
||
x := captcha.RangeRand(1, 20)
|
||
// 随机色
|
||
r := uint8(captcha.RangeRand(0, 200))
|
||
g := uint8(captcha.RangeRand(0, 200))
|
||
b := uint8(captcha.RangeRand(0, 200))
|
||
gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255})
|
||
gc.FillStringAt(string(code[l]), float64(x)+captcha.FontSize*float64(l), float64(int64(captcha.H)-y)+captcha.FontSize)
|
||
gc.Stroke()
|
||
}
|
||
}
|
||
|
||
// 验证码字符设置到图像上
|
||
func (captcha *Captcha) doFormula(gc *draw2dimg.GraphicContext, formulaArr []string) {
|
||
for l := 0; l < len(formulaArr); l++ {
|
||
y := captcha.RangeRand(0, 10)
|
||
x := captcha.RangeRand(5, 10)
|
||
|
||
// 随机色
|
||
r := uint8(captcha.RangeRand(10, 200))
|
||
g := uint8(captcha.RangeRand(10, 200))
|
||
b := uint8(captcha.RangeRand(10, 200))
|
||
|
||
gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255})
|
||
|
||
gc.FillStringAt(formulaArr[l], float64(x)+captcha.FontSize*float64(l), captcha.FontSize+float64(y))
|
||
gc.Stroke()
|
||
}
|
||
}
|
||
|
||
// 增加干扰线
|
||
func (captcha *Captcha) doLine(gc *draw2dimg.GraphicContext) {
|
||
// 设置干扰线
|
||
for n := 0; n < 3; n++ {
|
||
gc.SetLineWidth(float64(captcha.RangeRand(1, 2)))
|
||
//gc.SetLineWidth(1)
|
||
|
||
// 随机背景色
|
||
r := uint8(captcha.RangeRand(0, 255))
|
||
g := uint8(captcha.RangeRand(0, 255))
|
||
b := uint8(captcha.RangeRand(0, 255))
|
||
|
||
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
|
||
|
||
// 初始化位置
|
||
gc.MoveTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5)))
|
||
gc.LineTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5)))
|
||
|
||
gc.Stroke()
|
||
}
|
||
}
|
||
|
||
// 增加干扰点
|
||
func (captcha *Captcha) doPoint(gc *draw2dimg.GraphicContext) {
|
||
for n := 0; n < 10; n++ {
|
||
gc.SetLineWidth(float64(captcha.RangeRand(1, 3)))
|
||
|
||
// 随机色
|
||
r := uint8(captcha.RangeRand(0, 255))
|
||
g := uint8(captcha.RangeRand(0, 255))
|
||
b := uint8(captcha.RangeRand(0, 255))
|
||
|
||
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
|
||
|
||
x := captcha.RangeRand(0, int64(captcha.W)+10) + 1
|
||
y := captcha.RangeRand(0, int64(captcha.H)+5) + 1
|
||
|
||
gc.MoveTo(float64(x), float64(y))
|
||
gc.LineTo(float64(x+captcha.RangeRand(1, 2)), float64(y+captcha.RangeRand(1, 2)))
|
||
|
||
gc.Stroke()
|
||
}
|
||
}
|
||
|
||
// 增加正弦干扰线
|
||
func (captcha *Captcha) doSinLine(gc *draw2dimg.GraphicContext) {
|
||
h1 := captcha.RangeRand(-12, 12)
|
||
h2 := captcha.RangeRand(-1, 1)
|
||
w2 := captcha.RangeRand(5, 20)
|
||
h3 := captcha.RangeRand(5, 10)
|
||
|
||
h := float64(captcha.H)
|
||
w := float64(captcha.W)
|
||
|
||
// 随机色
|
||
r := uint8(captcha.RangeRand(128, 255))
|
||
g := uint8(captcha.RangeRand(128, 255))
|
||
b := uint8(captcha.RangeRand(128, 255))
|
||
|
||
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
|
||
gc.SetLineWidth(float64(captcha.RangeRand(1, 2)))
|
||
|
||
var i float64
|
||
for i = -w / 2; i < w/2; i = i + 0.1 {
|
||
y := h/float64(h3)*math.Sin(i/float64(w2)) + h/2 + float64(h1)
|
||
|
||
gc.LineTo(i+w/2, y)
|
||
|
||
if h2 == 0 {
|
||
gc.LineTo(i+w/2, y+float64(h2))
|
||
}
|
||
}
|
||
|
||
gc.Stroke()
|
||
}
|
||
|
||
// SetMode 设置模式
|
||
func (captcha *Captcha) SetMode(mode int) {
|
||
captcha.mode = mode
|
||
}
|
||
|
||
// SetFontPath 设置字体路径
|
||
func (captcha *Captcha) SetFontPath(FontPath string) {
|
||
captcha.FontPath = FontPath
|
||
}
|
||
|
||
// SetFontName 设置字体名称
|
||
func (captcha *Captcha) SetFontName(FontName string) {
|
||
captcha.FontName = FontName
|
||
}
|
||
|
||
// SetFontSize 设置字体大小
|
||
func (captcha *Captcha) SetFontSize(fontSize float64) {
|
||
captcha.FontSize = fontSize
|
||
}
|
||
|
||
// 设置相关字体
|
||
func (captcha *Captcha) setFont(gc *draw2dimg.GraphicContext) {
|
||
if captcha.FontPath == "" {
|
||
panic("the font path is empty!")
|
||
}
|
||
|
||
if captcha.FontName == "" {
|
||
panic("the font name is empty!")
|
||
}
|
||
// 字体文件
|
||
fontFile := filepath.Join(captcha.FontPath, captcha.FontName+".ttf")
|
||
fontBytes, err := os.ReadFile(fontFile)
|
||
if err != nil {
|
||
log.Println(err)
|
||
return
|
||
}
|
||
|
||
font, err := freetype.ParseFont(fontBytes)
|
||
if err != nil {
|
||
log.Println(err)
|
||
return
|
||
}
|
||
|
||
// 设置自定义字体相关信息
|
||
gc.FontCache = draw2d.NewSyncFolderFontCache(captcha.FontPath)
|
||
gc.FontCache.Store(draw2d.FontData{Name: captcha.FontName, Family: 0, Style: draw2d.FontStyleNormal}, font)
|
||
gc.SetFontData(draw2d.FontData{Name: captcha.FontName, Style: draw2d.FontStyleNormal})
|
||
|
||
//设置清晰度
|
||
if captcha.Dpi <= 0 {
|
||
captcha.Dpi = defaultDpi
|
||
}
|
||
gc.SetDPI(captcha.Dpi)
|
||
|
||
// 设置字体大小
|
||
if captcha.FontSize <= 0 {
|
||
captcha.FontSize = defaultFontSize
|
||
}
|
||
gc.SetFontSize(captcha.FontSize)
|
||
}
|