Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"captcha_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/interface/main/captcha/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"bilinear.go",
"business.go",
"captcha.go",
"draw.go",
"image.go",
"rotate.go",
"service.go",
],
importpath = "go-common/app/interface/main/captcha/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/interface/main/captcha/conf:go_default_library",
"//app/interface/main/captcha/dao:go_default_library",
"//library/cache:go_default_library",
"//library/ecode:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/golang/freetype:go_default_library",
"//vendor/github.com/golang/freetype/truetype:go_default_library",
"//vendor/github.com/satori/go.uuid:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,126 @@
package service
import (
"image"
"image/color"
"math"
)
var bili = Bilinear{}
// Bilinear Bilinear.
type Bilinear struct{}
// BilinearSrc BilinearSrc.
type BilinearSrc struct {
// Top-left and bottom-right interpolation sources
low, high image.Point
// Fraction of each pixel to take. The 0 suffix indicates
// top/left, and the 1 suffix indicates bottom/right.
frac00, frac01, frac10, frac11 float64
}
// RGBA RGBA.
func (Bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA {
p := findLinearSrc(src.Bounds(), x, y)
// Array offsets for the surrounding pixels.
off00 := offRGBA(src, p.low.X, p.low.Y)
off01 := offRGBA(src, p.high.X, p.low.Y)
off10 := offRGBA(src, p.low.X, p.high.Y)
off11 := offRGBA(src, p.high.X, p.high.Y)
var fr, fg, fb, fa float64
fr += float64(src.Pix[off00+0]) * p.frac00
fg += float64(src.Pix[off00+1]) * p.frac00
fb += float64(src.Pix[off00+2]) * p.frac00
fa += float64(src.Pix[off00+3]) * p.frac00
fr += float64(src.Pix[off01+0]) * p.frac01
fg += float64(src.Pix[off01+1]) * p.frac01
fb += float64(src.Pix[off01+2]) * p.frac01
fa += float64(src.Pix[off01+3]) * p.frac01
fr += float64(src.Pix[off10+0]) * p.frac10
fg += float64(src.Pix[off10+1]) * p.frac10
fb += float64(src.Pix[off10+2]) * p.frac10
fa += float64(src.Pix[off10+3]) * p.frac10
fr += float64(src.Pix[off11+0]) * p.frac11
fg += float64(src.Pix[off11+1]) * p.frac11
fb += float64(src.Pix[off11+2]) * p.frac11
fa += float64(src.Pix[off11+3]) * p.frac11
var c color.RGBA
c.R = uint8(fr + 0.5)
c.G = uint8(fg + 0.5)
c.B = uint8(fb + 0.5)
c.A = uint8(fa + 0.5)
return c
}
func findLinearSrc(b image.Rectangle, sx, sy float64) BilinearSrc {
maxX := float64(b.Max.X)
maxY := float64(b.Max.Y)
minX := float64(b.Min.X)
minY := float64(b.Min.Y)
lowX := math.Floor(sx - 0.5)
lowY := math.Floor(sy - 0.5)
if lowX < minX {
lowX = minX
}
if lowY < minY {
lowY = minY
}
highX := math.Ceil(sx - 0.5)
highY := math.Ceil(sy - 0.5)
if highX >= maxX {
highX = maxX - 1
}
if highY >= maxY {
highY = maxY - 1
}
// In the variables below, the 0 suffix indicates top/left, and the
// 1 suffix indicates bottom/right.
// Center of each surrounding pixel.
x00 := lowX + 0.5
y00 := lowY + 0.5
x01 := highX + 0.5
y01 := lowY + 0.5
x10 := lowX + 0.5
y10 := highY + 0.5
x11 := highX + 0.5
y11 := highY + 0.5
p := BilinearSrc{
low: image.Pt(int(lowX), int(lowY)),
high: image.Pt(int(highX), int(highY)),
}
// Literally, edge cases. If we are close enough to the edge of
// the image, curtail the interpolation sources.
if lowX == highX && lowY == highY {
p.frac00 = 1.0
} else if sy-minY <= 0.5 && sx-minX <= 0.5 {
p.frac00 = 1.0
} else if maxY-sy <= 0.5 && maxX-sx <= 0.5 {
p.frac11 = 1.0
} else if sy-minY <= 0.5 || lowY == highY {
p.frac00 = x01 - sx
p.frac01 = sx - x00
} else if sx-minX <= 0.5 || lowX == highX {
p.frac00 = y10 - sy
p.frac10 = sy - y00
} else if maxY-sy <= 0.5 {
p.frac10 = x11 - sx
p.frac11 = sx - x10
} else if maxX-sx <= 0.5 {
p.frac01 = y11 - sy
p.frac11 = sy - y01
} else {
p.frac00 = (x01 - sx) * (y10 - sy)
p.frac01 = (sx - x00) * (y11 - sy)
p.frac10 = (x11 - sx) * (sy - y00)
p.frac11 = (sx - x10) * (sy - y01)
}
return p
}

View File

@@ -0,0 +1,29 @@
package service
import (
"time"
"go-common/app/interface/main/captcha/conf"
xtime "go-common/library/time"
)
var (
_defaultBusiness = &conf.Business{
BusinessID: "default",
LenStart: 4,
LenEnd: 4,
Width: 100,
Length: 50,
TTL: xtime.Duration(300 * time.Second),
}
)
// LookUp look up business services.
func (s *Service) LookUp(bid string) (business *conf.Business) {
for _, b := range s.conf.Business {
if b.BusinessID == bid {
return b
}
}
return _defaultBusiness
}

View File

@@ -0,0 +1,112 @@
package service
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"image/jpeg"
"image/png"
"math/rand"
"strings"
"sync"
"time"
"go-common/library/ecode"
uuid "github.com/satori/go.uuid"
)
const (
_captchaURL = "%s/x/v1/captcha/get?bid=%s&token=%s"
)
// Token use bid, get a token.
func (s *Service) Token(c context.Context, bid string) (url string, token string, err error) {
token = hex.EncodeToString(uuid.NewV4().Bytes())
business := s.LookUp(bid)
if err = s.dao.AddTokenCache(c, token, int32(time.Duration(business.TTL)/time.Second)); err != nil {
return
}
url = fmt.Sprintf(_captchaURL, s.conf.Captcha.OuterHost, bid, token)
return
}
// CaptchaImg get a captcha by token,bid.
func (s *Service) CaptchaImg(c context.Context, token, bid string) (img []byte, err error) {
code, img, ttl := s.randomCaptcha(bid)
realCode, _, err := s.dao.CaptchaCache(c, token)
if err != nil {
return
}
if realCode == "" {
err = ecode.CaptchaTokenExpired
return
}
err = s.dao.UpdateTokenCache(c, token, code, ttl)
return
}
// VerifyCaptcha verify captcha by token and code.
func (s *Service) VerifyCaptcha(c context.Context, token, code string) (err error) {
var (
realCode string
isInit bool
)
if realCode, isInit, err = s.dao.CaptchaCache(c, token); err != nil {
return
}
if realCode == "" {
err = ecode.CaptchaCodeNotFound
return
}
if isInit {
err = ecode.CaptchaNotCreate
return
}
if ok := strings.ToLower(realCode) == strings.ToLower(code); ok {
s.cacheCh.Save(func() {
s.dao.DelCaptchaCache(context.Background(), token)
})
} else {
err = ecode.CaptchaErr
}
return
}
func (s *Service) initGenerater(waiter *sync.WaitGroup, bid string, lenStart, lenEnd, width, length int) {
s.generater(bid, lenStart, lenEnd, width, length)
waiter.Done()
}
func (s *Service) generater(bid string, lenStart, lenEnd, width, length int) {
images := make(map[string][]byte, s.conf.Captcha.Capacity)
codes := make([]string, 0, s.conf.Captcha.Capacity)
for i := 0; i < s.conf.Captcha.Capacity; i++ {
img, code := s.captcha.createImage(lenStart, lenEnd, width, length, TypeALL)
var b bytes.Buffer
switch s.conf.Captcha.Ext {
case "png":
png.Encode(&b, img)
case "jpeg":
jpeg.Encode(&b, img, &jpeg.Options{Quality: 100})
default:
jpeg.Encode(&b, img, &jpeg.Options{Quality: 100})
}
images[code] = b.Bytes()
codes = append(codes, code)
}
s.lock.Lock()
s.mImage[bid] = images
s.mCode[bid] = codes
s.lock.Unlock()
}
func (s *Service) randomCaptcha(bid string) (code string, img []byte, ttl int32) {
business := s.LookUp(bid)
ttl = int32(time.Duration(business.TTL) / time.Second)
rnd := rand.Intn(s.conf.Captcha.Capacity)
code = s.mCode[business.BusinessID][rnd]
img = s.mImage[business.BusinessID][code]
return
}

View File

@@ -0,0 +1,32 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
var (
bid = "account"
token = "5049a45ffc7c49489c14a7677c4548e2"
)
func TestToken(t *testing.T) {
var (
c = context.Background()
)
Convey("err should return nil", t, func() {
_, t, err := svr.Token(c, bid)
So(err, ShouldBeNil)
So(t, ShouldNotBeNil)
})
Convey("err should return nil", t, func() {
err := svr.VerifyCaptcha(c, token, "test")
So(err, ShouldNotBeNil)
})
Convey("err should return nil", t, func() {
business := svr.LookUp(bid)
So(business, ShouldNotBeEmpty)
})
}

View File

@@ -0,0 +1,247 @@
package service
import (
"image"
"image/color"
"image/draw"
"io/ioutil"
"math"
"math/rand"
"time"
"go-common/app/interface/main/captcha/conf"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
// CONST VALUE.
const (
NORMAL = int(4)
MEDIUM = int(8)
HIGH = int(16)
MinLenStart = int(4)
MinWidth = int(48)
MinLength = int(20)
Length48 = int(48)
TypeNone = int(0)
TypeLOWER = int(1)
TypeUPPER = int(2)
TypeALL = int(3)
)
var fontKinds = [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}
func sign(x int) int {
if x > 0 {
return 1
}
return -1
}
// NewCaptcha new a captcha.
func newCaptcha(c *conf.Captcha) *Captcha {
captcha := &Captcha{
disturbLevel: NORMAL,
}
captcha.frontColors = []color.Color{color.Black}
captcha.bkgColors = []color.Color{color.White}
captcha.setFont(c.Fonts...)
colors := []color.Color{}
for _, v := range c.BkgColors {
colors = append(colors, v)
}
captcha.setBkgColor(colors...)
colors = []color.Color{}
for _, v := range c.FrontColors {
colors = append(colors, v)
}
captcha.setFontColor(colors...)
captcha.setDisturbance(c.DisturbLevel)
return captcha
}
// addFont add font.
func (c *Captcha) addFont(path string) error {
fontdata, erro := ioutil.ReadFile(path)
if erro != nil {
return erro
}
font, erro := freetype.ParseFont(fontdata)
if erro != nil {
return erro
}
if c.fonts == nil {
c.fonts = []*truetype.Font{}
}
c.fonts = append(c.fonts, font)
return nil
}
// setFont set font.
func (c *Captcha) setFont(paths ...string) (err error) {
for _, v := range paths {
if err = c.addFont(v); err != nil {
return err
}
}
return nil
}
// setBkgColor set backgroud color.
func (c *Captcha) setBkgColor(colors ...color.Color) {
if len(colors) > 0 {
c.bkgColors = c.bkgColors[:0]
c.bkgColors = append(c.bkgColors, colors...)
}
}
func (c *Captcha) randFont() *truetype.Font {
return c.fonts[rand.Intn(len(c.fonts))]
}
// setBkgsetFontColorColor set font color.
func (c *Captcha) setFontColor(colors ...color.Color) {
if len(colors) > 0 {
c.frontColors = c.frontColors[:0]
c.frontColors = append(c.frontColors, colors...)
}
}
// setDisturbance set disturbance.
func (c *Captcha) setDisturbance(d int) {
if d > 0 {
c.disturbLevel = d
}
}
func (c *Captcha) createImage(lenStart, lenEnd, width, length, t int) (image *Image, str string) {
num := MinLenStart
if lenStart < MinLenStart {
lenStart = MinLenStart
}
if lenEnd > lenStart {
// rand.Seed(time.Now().UnixNano())
num = rand.Intn(lenEnd-lenStart+1) + lenStart
}
str = c.randStr(num, t)
return c.createCustom(str, width, length), str
}
func (c *Captcha) createCustom(str string, width, length int) *Image {
// boundary check
if len(str) == 0 {
str = "bilibili"
}
if width < MinWidth {
width = MinWidth
}
if length < MinLength {
length = MinLength
}
dst := newImage(width, length)
c.drawBkg(dst)
c.drawNoises(dst)
c.drawString(dst, str, width, length)
return dst
}
// randStr ascII random
// 48~57 -> 0~9 number
// 65~90 -> A~Z uppercase
// 98~122 -> a~z lowcase
func (c *Captcha) randStr(size, kind int) string {
ikind, result := kind, make([]byte, size)
isAll := kind > TypeUPPER || kind < TypeNone
// rand.Seed(time.Now().UnixNano())
for i := 0; i < size; i++ {
if isAll {
ikind = rand.Intn(TypeALL)
}
scope, base := fontKinds[ikind][0], fontKinds[ikind][1]
result[i] = uint8(base + rand.Intn(scope))
}
return string(result)
}
func (c *Captcha) drawBkg(img *Image) {
ra := rand.New(rand.NewSource(time.Now().UnixNano()))
//填充主背景色
bgcolorindex := ra.Intn(len(c.bkgColors))
bkg := image.NewUniform(c.bkgColors[bgcolorindex])
img.fillBkg(bkg)
}
func (c *Captcha) drawNoises(img *Image) {
ra := rand.New(rand.NewSource(time.Now().UnixNano()))
//// 待绘制图片的尺寸
point := img.Bounds().Size()
disturbLevel := c.disturbLevel
// 绘制干扰斑点
for i := 0; i < disturbLevel; i++ {
x := ra.Intn(point.X)
y := ra.Intn(point.Y)
radius := ra.Intn(point.Y/20) + 1
colorindex := ra.Intn(len(c.frontColors))
img.drawCircle(x, y, radius, i%4 != 0, c.frontColors[colorindex])
}
// 绘制干扰线
for i := 0; i < disturbLevel; i++ {
x := ra.Intn(point.X)
y := ra.Intn(point.Y)
o := int(math.Pow(-1, float64(i)))
w := ra.Intn(point.Y) * o
h := ra.Intn(point.Y/10) * o
colorindex := ra.Intn(len(c.frontColors))
img.drawLine(x, y, x+w, y+h, c.frontColors[colorindex])
colorindex++
}
}
// 绘制文字
func (c *Captcha) drawString(img *Image, str string, width, length int) {
if c.fonts == nil {
panic("没有设置任何字体")
}
tmp := newImage(width, length)
// 文字大小为图片高度的 0.6
fsize := int(float64(length) * 0.6)
// 用于生成随机角度
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// 文字之间的距离
// 左右各留文字的1/4大小为内部边距
padding := fsize / 4
gap := (width - padding*2) / (len(str))
// 逐个绘制文字到图片上
for i, char := range str {
// 创建单个文字图片
// 以文字为尺寸创建正方形的图形
str := newImage(fsize, fsize)
// str.FillBkg(image.NewUniform(color.Black))
// 随机取一个前景色
colorindex := r.Intn(len(c.frontColors))
//随机取一个字体
font := c.randFont()
str.drawString(font, c.frontColors[colorindex], string(char), float64(fsize))
// 转换角度后的文字图形
rs := str.rotate(float64(r.Intn(40) - 20))
// 计算文字位置
s := rs.Bounds().Size()
left := i*gap + padding
top := (length - s.Y) / 2
// 绘制到图片上
draw.Draw(tmp, image.Rect(left, top, left+s.X, top+s.Y), rs, image.ZP, draw.Over)
}
if length >= Length48 {
// 高度大于48添加波纹 小于48波纹影响用户识别
tmp.distortTo(float64(fsize)/10, 200.0)
}
draw.Draw(img, tmp.Bounds(), tmp, image.ZP, draw.Over)
}

View File

@@ -0,0 +1,132 @@
package service
import (
"image"
"image/color"
"image/draw"
"math"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
// Image Image.
type Image struct {
*image.RGBA
}
// newImage create a new image
func newImage(width, length int) *Image {
img := &Image{image.NewRGBA(image.Rect(0, 0, width, length))}
return img
}
func (img *Image) fillBkg(c image.Image) {
draw.Draw(img, img.Bounds(), c, image.ZP, draw.Over)
}
// drawCircle draw circle.
func (img *Image) drawCircle(cx, cy, radius int, isFillColor bool, color color.Color) {
point := img.Bounds().Size()
// 如果圆在图片可见区域外,直接退出
if cx+radius < 0 || cx-radius >= point.X || cy+radius < 0 || cy-radius >= point.Y {
return
}
x, y, d := 0, radius, 3-2*radius
for x <= y {
if isFillColor {
for yi := x; yi <= y; yi++ {
img.drawCircle8(cx, cy, x, yi, color)
}
} else {
img.drawCircle8(cx, cy, x, y, color)
}
if d < 0 {
d = d + 4*x + 6
} else {
d = d + 4*(x-y) + 10
y--
}
x++
}
}
// drawLine .
// Bresenham算法(https://zh.wikipedia.org/zh-cn/布雷森漢姆直線演算法).
// startX,startY 起点 endX,endY终点.
func (img *Image) drawLine(startX, startY, endX, endY int, color color.Color) {
dx, dy, flag := int(math.Abs(float64(startY-startX))), int(math.Abs(float64(endY-startY))), false
if dy > dx {
flag = true
startX, startY = startY, startX
endX, endY = endY, endX
dx, dy = dy, dx
}
ix, iy := sign(endX-startX), sign(endY-startY)
n2dy := dy * 2
n2dydx := (dy - dx) * 2
d := n2dy - dx
for startX != endX {
if d < 0 {
d += n2dy
} else {
startY += iy
d += n2dydx
}
if flag {
img.Set(startY, startX, color)
} else {
img.Set(startX, startY, color)
}
startX += ix
}
}
func (img *Image) drawCircle8(xc, yc, x, y int, color color.Color) {
img.Set(xc+x, yc+y, color)
img.Set(xc-x, yc+y, color)
img.Set(xc+x, yc-y, color)
img.Set(xc-x, yc-y, color)
img.Set(xc+y, yc+x, color)
img.Set(xc-y, yc+x, color)
img.Set(xc+y, yc-x, color)
img.Set(xc-y, yc-x, color)
}
// drawString image draw string.
func (img *Image) drawString(font *truetype.Font, color color.Color, str string, fontsize float64) {
ctx := freetype.NewContext()
// default 72dpi
ctx.SetDst(img)
ctx.SetClip(img.Bounds())
ctx.SetSrc(image.NewUniform(color))
ctx.SetFontSize(fontsize)
ctx.SetFont(font)
// 写入文字的位置
pt := freetype.Pt(0, int(-fontsize/6)+ctx.PointToFixed(fontsize).Ceil())
ctx.DrawString(str, pt)
}
// 水波纹, amplude=振幅, period=周期
// copy from https://github.com/dchest/captcha/blob/master/image.go
func (img *Image) distortTo(amplude float64, period float64) {
w := img.Bounds().Max.X
h := img.Bounds().Max.Y
oldm := img.RGBA
dx := 1.4 * math.Pi / period
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
xo := amplude * math.Sin(float64(y)*dx)
yo := amplude * math.Cos(float64(x)*dx)
rgba := oldm.RGBAAt(x+int(xo), y+int(yo))
if rgba.A > 0 {
oldm.SetRGBA(x, y, rgba)
}
}
}
}
// Rotate 旋转
func (img *Image) rotate(angle float64) image.Image {
return new(rotate).rotate(angle, img.RGBA).transformRGBA()
}

View File

@@ -0,0 +1,90 @@
package service
import (
"image"
"math"
)
type rotate struct {
dx float64
dy float64
sin float64
cos float64
neww float64
newh float64
src *image.RGBA
}
func (r *rotate) rotate(angle float64, src *image.RGBA) *rotate {
r.src = src
srsize := src.Bounds().Size()
width, height := srsize.X, srsize.Y
// 源图四个角的坐标(以图像中心为坐标系原点)
// 左下角,右下角,左上角,右上角
srcwp, srchp := float64(width)*0.5, float64(height)*0.5
srcx1, srcy1 := -srcwp, srchp
srcx2, srcy2 := srcwp, srchp
srcx3, srcy3 := -srcwp, -srchp
srcx4, srcy4 := srcwp, -srchp
r.sin, r.cos = math.Sincos(radian(angle))
// 旋转后的四角坐标
desx1, desy1 := r.cos*srcx1+r.sin*srcy1, -r.sin*srcx1+r.cos*srcy1
desx2, desy2 := r.cos*srcx2+r.sin*srcy2, -r.sin*srcx2+r.cos*srcy2
desx3, desy3 := r.cos*srcx3+r.sin*srcy3, -r.sin*srcx3+r.cos*srcy3
desx4, desy4 := r.cos*srcx4+r.sin*srcy4, -r.sin*srcx4+r.cos*srcy4
// 新的高度很宽度
r.neww = math.Max(math.Abs(desx4-desx1), math.Abs(desx3-desx2)) + 0.5
r.newh = math.Max(math.Abs(desy4-desy1), math.Abs(desy3-desy2)) + 0.5
r.dx = -0.5*r.neww*r.cos - 0.5*r.newh*r.sin + srcwp
r.dy = 0.5*r.neww*r.sin - 0.5*r.newh*r.cos + srchp
return r
}
func radian(angle float64) float64 {
return angle * math.Pi / 180.0
}
func (r *rotate) transformRGBA() image.Image {
srcb := r.src.Bounds()
b := image.Rect(0, 0, int(r.neww), int(r.newh))
dst := image.NewRGBA(b)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
sx, sy := r.pt(x, y)
if inBounds(srcb, sx, sy) {
// 消除锯齿填色
c := bili.RGBA(r.src, sx, sy)
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
dst.Pix[off+0] = c.R
dst.Pix[off+1] = c.G
dst.Pix[off+2] = c.B
dst.Pix[off+3] = c.A
}
}
}
return dst
}
func (r *rotate) pt(x, y int) (float64, float64) {
return float64(-y)*r.sin + float64(x)*r.cos + r.dy,
float64(y)*r.cos + float64(x)*r.sin + r.dx
}
func inBounds(b image.Rectangle, x, y float64) bool {
if x < float64(b.Min.X) || x >= float64(b.Max.X) {
return false
}
if y < float64(b.Min.Y) || y >= float64(b.Max.Y) {
return false
}
return true
}
func offRGBA(src *image.RGBA, x, y int) int {
return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
}

View File

@@ -0,0 +1,79 @@
package service
import (
"context"
"image/color"
"sync"
"time"
"go-common/app/interface/main/captcha/conf"
"go-common/app/interface/main/captcha/dao"
"go-common/library/cache"
"go-common/library/ecode"
"github.com/golang/freetype/truetype"
)
// Captcha captcha.
type Captcha struct {
frontColors []color.Color
bkgColors []color.Color
disturbLevel int
fonts []*truetype.Font
}
// Service captcha service.
type Service struct {
conf *conf.Config
dao *dao.Dao
captcha *Captcha
cacheCh *cache.Cache
// captcha mem.
init bool
lock sync.RWMutex
mCode map[string][]string
mImage map[string]map[string][]byte
}
// New new a service.
func New(c *conf.Config) (s *Service) {
s = &Service{
conf: c,
dao: dao.New(c),
captcha: newCaptcha(c.Captcha),
cacheCh: cache.New(1, 1024),
mCode: make(map[string][]string, len(c.Business)),
mImage: make(map[string]map[string][]byte, len(c.Business)),
}
go s.generaterProc()
return s
}
// Close close all dao.
func (s *Service) Close() {
s.dao.Close()
}
// Ping ping dao.
func (s *Service) Ping(c context.Context) error {
if !s.init {
return ecode.CaptchaNotCreate
}
return s.dao.Ping(c)
}
func (s *Service) generaterProc() {
waiter := &sync.WaitGroup{}
for _, b := range s.conf.Business {
waiter.Add(1)
go s.initGenerater(waiter, b.BusinessID, b.LenStart, b.LenEnd, b.Width, b.Length)
}
waiter.Wait()
s.init = true
for {
for _, b := range s.conf.Business {
go s.generater(b.BusinessID, b.LenStart, b.LenEnd, b.Width, b.Length)
}
time.Sleep(time.Duration(s.conf.Captcha.Interval))
}
}

View File

@@ -0,0 +1,19 @@
package service
import (
"flag"
"path/filepath"
"testing"
"go-common/app/interface/main/captcha/conf"
)
var svr *Service
func TestMain(m *testing.M) {
flag.Parse()
dir, _ := filepath.Abs("../cmd/captcha-test.toml")
flag.Set("conf", dir)
conf.Init()
svr = New(conf.Conf)
}