李伟乐 2 years ago
parent a02810700c
commit 031341d808
  1. 8
      go.mod
  2. 65
      go.sum
  3. 21
      pkg/base64Captcha/_examples/.travis.yml
  4. 100
      pkg/base64Captcha/_examples/main.go
  5. BIN
      pkg/base64Captcha/_examples/static/108360.png
  6. BIN
      pkg/base64Captcha/_examples/static/1lNMVxfysfSQJXvjR1LX.wav
  7. BIN
      pkg/base64Captcha/_examples/static/28+58.png
  8. BIN
      pkg/base64Captcha/_examples/static/99+73.png
  9. BIN
      pkg/base64Captcha/_examples/static/ACNRfd.png
  10. BIN
      pkg/base64Captcha/_examples/static/captcha.png
  11. BIN
      pkg/base64Captcha/_examples/static/ctOv6N.png
  12. BIN
      pkg/base64Captcha/_examples/static/gGncJC.png
  13. 620
      pkg/base64Captcha/_examples/static/index.html
  14. BIN
      pkg/base64Captcha/_examples/static/rW4npZ.png
  15. 58
      pkg/base64Captcha/captcha.go
  16. 138
      pkg/base64Captcha/captcha_test.go
  17. 60
      pkg/base64Captcha/const.go
  18. 36
      pkg/base64Captcha/driver_audio.go
  19. 89
      pkg/base64Captcha/driver_audio_test.go
  20. 146
      pkg/base64Captcha/driver_chinese.go
  21. 104
      pkg/base64Captcha/driver_chinese_test.go
  22. 80
      pkg/base64Captcha/driver_digit.go
  23. 110
      pkg/base64Captcha/driver_digit_test.go
  24. 123
      pkg/base64Captcha/driver_language.go
  25. 96
      pkg/base64Captcha/driver_language_test.go
  26. 143
      pkg/base64Captcha/driver_math.go
  27. 124
      pkg/base64Captcha/driver_math_test.go
  28. 130
      pkg/base64Captcha/driver_string.go
  29. 147
      pkg/base64Captcha/driver_string_test.go
  30. 27
      pkg/base64Captcha/faq_test.go
  31. 239
      pkg/base64Captcha/fonts.go
  32. BIN
      pkg/base64Captcha/fonts/3Dumb.ttf
  33. BIN
      pkg/base64Captcha/fonts/ApothecaryFont.ttf
  34. BIN
      pkg/base64Captcha/fonts/Comismsh.ttf
  35. BIN
      pkg/base64Captcha/fonts/DENNEthree-dee.ttf
  36. BIN
      pkg/base64Captcha/fonts/DeborahFancyDress.ttf
  37. BIN
      pkg/base64Captcha/fonts/Flim-Flam.ttf
  38. BIN
      pkg/base64Captcha/fonts/RitaSmith.ttf
  39. BIN
      pkg/base64Captcha/fonts/actionj.ttf
  40. BIN
      pkg/base64Captcha/fonts/chromohv.ttf
  41. 7
      pkg/base64Captcha/fonts/readme.md
  42. BIN
      pkg/base64Captcha/fonts/wqy-microhei.ttc
  43. 44
      pkg/base64Captcha/fonts_embedded.go
  44. 10
      pkg/base64Captcha/fonts_embedded_default.go
  45. 52
      pkg/base64Captcha/fonts_test.go
  46. 9
      pkg/base64Captcha/interface_driver.go
  47. 12
      pkg/base64Captcha/interface_fonts.go
  48. 11
      pkg/base64Captcha/interface_item.go
  49. 20
      pkg/base64Captcha/interface_store.go
  50. 169
      pkg/base64Captcha/item_audio.go
  51. 122
      pkg/base64Captcha/item_audio_test.go
  52. 258
      pkg/base64Captcha/item_char.go
  53. 213
      pkg/base64Captcha/item_char_test.go
  54. 258
      pkg/base64Captcha/item_digit.go
  55. 102
      pkg/base64Captcha/item_digit_test.go
  56. 115
      pkg/base64Captcha/random_math.go
  57. 183
      pkg/base64Captcha/random_math_test.go
  58. 13908
      pkg/base64Captcha/sounds.go
  59. 116
      pkg/base64Captcha/store_memory.go
  60. 12
      pkg/base64Captcha/store_memory_default.go
  61. 164
      pkg/base64Captcha/store_memory_test.go
  62. 63
      pkg/base64Captcha/store_sync_map.go
  63. 161
      pkg/base64Captcha/store_sync_map_test.go
  64. 94
      pkg/base64Captcha/util.go
  65. 77
      pkg/base64Captcha/util_audio.go
  66. 103
      pkg/base64Captcha/util_audio_test.go
  67. 80
      pkg/base64Captcha/util_test.go
  68. 75
      pkg/captcha/captcha.go
  69. 15
      pkg/captcha/captcha_test.go

@ -8,13 +8,15 @@ require (
github.com/envoyproxy/protoc-gen-validate v0.9.1
github.com/gin-gonic/gin v1.9.0
github.com/go-playground/form v3.1.4+incompatible
github.com/go-redis/redis v6.15.9+incompatible
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/golang/protobuf v1.5.2
github.com/gorilla/mux v1.8.0
github.com/mojocn/base64Captcha v1.3.5
github.com/nsqio/go-nsq v1.1.0
github.com/pkg/errors v0.9.1
github.com/robfig/cron v1.2.0
github.com/sirupsen/logrus v1.9.0
golang.org/x/image v0.5.0
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1
@ -28,7 +30,6 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
@ -36,12 +37,13 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.27.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.10 // indirect
golang.org/x/arch v0.2.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect

@ -13,6 +13,9 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY=
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
@ -26,21 +29,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -57,10 +74,19 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.27.2 h1:SKU0CXeKE/WVgIV1T61kSa3+IRE8Ekrv9rdXDwwTqnY=
github.com/onsi/gomega v1.27.2/go.mod h1:5mR3phAHpkAVIDkHEUBY6HGVsU+cpcEscrGPB4oPlZI=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -77,6 +103,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -87,27 +114,44 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -125,21 +169,36 @@ golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5 h1:/cadn7taPtPlCgiWNetEPsle7jgnlad2R7gR5MXB6dM=
google.golang.org/genproto v0.0.0-20230301171018-9ab4bdc49ad5/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1,21 @@
language: go
go:
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
email:
recipients:
- neochau@gmail.com
on_success: always

@ -0,0 +1,100 @@
// example of HTTP server that uses the captcha package.
package main
import (
"encoding/json"
"fmt"
"git.diulo.com/mogfee/protoc-gen-kit/pkg/base64Captcha"
"log"
"net/http"
)
// configJsonBody json request body.
type configJsonBody struct {
Id string
CaptchaType string
VerifyValue string
DriverAudio *base64Captcha.DriverAudio
DriverString *base64Captcha.DriverString
DriverChinese *base64Captcha.DriverChinese
DriverMath *base64Captcha.DriverMath
DriverDigit *base64Captcha.DriverDigit
}
var store = base64Captcha.DefaultMemStore
// base64Captcha create http handler
func generateCaptchaHandler(w http.ResponseWriter, r *http.Request) {
//parse request parameters
decoder := json.NewDecoder(r.Body)
var param configJsonBody
err := decoder.Decode(&param)
if err != nil {
log.Println(err)
}
defer r.Body.Close()
var driver base64Captcha.Driver
//choose driver
switch param.CaptchaType {
case "audio":
driver = param.DriverAudio
case "string":
driver = param.DriverString.ConvertFonts()
case "math":
driver = param.DriverMath.ConvertFonts()
case "chinese":
driver = param.DriverChinese.ConvertFonts()
default:
driver = param.DriverDigit
}
c := base64Captcha.NewCaptcha(driver, store)
id, b64s, err := c.Generate()
fmt.Println(id, b64s, err)
body := map[string]interface{}{"code": 1, "data": b64s, "captchaId": id, "msg": "success"}
if err != nil {
body = map[string]interface{}{"code": 0, "msg": err.Error()}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(body)
}
// base64Captcha verify http handler
func captchaVerifyHandle(w http.ResponseWriter, r *http.Request) {
//parse request parameters
decoder := json.NewDecoder(r.Body)
var param configJsonBody
err := decoder.Decode(&param)
if err != nil {
log.Println(err)
}
defer r.Body.Close()
//verify the captcha
body := map[string]interface{}{"code": 0, "msg": "failed"}
if store.Verify(param.Id, param.VerifyValue, true) {
body = map[string]interface{}{"code": 1, "msg": "ok"}
}
//set json response
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(body)
}
// start a net/http server
func main() {
//serve Vuejs+ElementUI+Axios Web Application
http.Handle("/", http.FileServer(http.Dir("./static")))
//api for create captcha
http.HandleFunc("/api/getCaptcha", generateCaptchaHandler)
//api for verify captcha
http.HandleFunc("/api/verifyCaptcha", captchaVerifyHandle)
fmt.Println("Server is at :8777")
if err := http.ListenAndServe(":8777", nil); err != nil {
log.Fatal(err)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,620 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Config Parameter Playground</title>
<meta name="Keywords" content="golang,godoc,captcha,base64,png,图像验证码"/>
<meta name="Description" content="Base64 Captcha"/>
<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.0.11/theme-chalk/index.css">
<style>
.el-header, .el-footer {
background-color: #B3C0D1;
color: #333;
text-align: center;
line-height: 0px;
}
.el-header > p {
margin-top: 12px !important;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
/*line-height: 160px;*/
}
body {
margin: 0px;
text-align: center;
}
.login-container {
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
margin: 15px auto auto auto;
width: 480px;
padding: 6px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.title {
margin: 0px auto 20px auto;
text-align: center;
color: #505458;
}
.captcha-img {
cursor: pointer;
position: relative;
border: 1px solid chartreuse;
box-shadow: 0 0 6px #cac6c6;
}
.el-form-item {
margin-bottom: 0px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
padding: 0px !important;
}
</style>
<!-- Place this tag in your head or just before your close body tag. -->
<script src="https://buttons.github.io/buttons.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.0.11/index.js"></script>
<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script>
</head>
<body>
<div id="app">
<el-container>
<el-header
style="height: 90px!important;">
<!-- Place this tag where you want the button to render. -->
<p>
<a class="github-button" href="https://github.com/mojocn/base64captcha" data-size="large"
data-show-count="true" aria-label="Star mojocn/base64captcha on GitHub">Star</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/mojocn" data-size="large" data-show-count="true"
aria-label="Follow @mojocn on GitHub">Follow @mojocn</a>
<a class="github-button" href="https://github.com/JJJJJJJerk" data-size="large" data-show-count="true"
aria-label="Follow @mojocn on GitHub">Follow @Eric Zhou</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/mojocn/base64captcha/issues" data-size="large"
data-show-count="true" aria-label="Issue mojocn/base64captcha on GitHub">Issue</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/mojocn/base64captcha/archive/master.zip"
data-size="large" aria-label="Download mojocn/base64captcha on GitHub">Download</a>
</p>
<a href="https://godoc.org/github.com/mojocn/base64Captcha" rel="nofollow"><img
src="https://camo.githubusercontent.com/600bdcf87a3b63b5300c6673401901196360a82a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f6d6f6a6f636e2f626173653634436170746368613f7374617475732e737667"
alt="GoDoc" data-canonical-src="https://godoc.org/github.com/mojocn/base64Captcha?status.svg"
style="max-width:100%;"></a>
<a href="https://goreportcard.com/report/github.com/mojocn/base64Captcha" rel="nofollow"><img
src="https://camo.githubusercontent.com/0848346ead4693b8b2d975d8cbbb032945fb708d/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f6d6f6a6f636e2f62617365363443617074636861"
alt="Go Report Card"
data-canonical-src="https://goreportcard.com/badge/github.com/mojocn/base64Captcha"
style="max-width:100%;">
</a>
<a href="http://golangfoundation.org" rel="nofollow"><img
src="https://camo.githubusercontent.com/36f4996a1c92724272c100659936593ff0909a29/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f6c616e672d466f756e646174696f6e2d677265656e2e737667"
alt="Foundation" data-canonical-src="https://img.shields.io/badge/Golang-Foundation-green.svg"
style="max-width:100%;"></a>
<a href="https://codecov.io/gh/mojocn/base64Captcha">
<img src="https://codecov.io/gh/mojocn/base64Captcha/branch/master/graph/badge.svg"/>
</a>
<a href="http://doge.mit-license.org"><img
src="https://camo.githubusercontent.com/3d7aa1ddbfa86368152bf42123c17b69ea8070be/687474703a2f2f696d672e736869656c64732e696f2f3a6c6963656e73652d6d69742d626c75652e737667"
alt="License" data-canonical-src="http://img.shields.io/:license-mit-blue.svg"
style="max-width:100%;"></a>
<a href="https://camo.githubusercontent.com/69f50fbca17d6577018651ff9afcb55cdac03bc4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73746162696c6974792d737461626c652d627269676874677265656e2e737667"
target="_blank"><img
src="https://camo.githubusercontent.com/69f50fbca17d6577018651ff9afcb55cdac03bc4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73746162696c6974792d737461626c652d627269676874677265656e2e737667"
alt="stability-stable"
data-canonical-src="https://img.shields.io/badge/stability-stable-brightgreen.svg"
style="max-width:100%;"></a>
</p>
</el-header>
<el-main>
<el-row style="width: 50%;margin:8px auto;background: #9e9e9e;padding: 8px 2rem" type="flex"
justify="center" align="middle">
<el-col :span="16">
<img @click.prevent="generateCaptcha" :src="blob" class="captcha-img"
v-if="form.CaptchaType !== 'audio'"/>
<audio controls :src="blob" autoplay v-if="form.CaptchaType === 'audio'"/>
</el-col>
<el-col :span="8">
<el-form>
<el-form-item>
<el-input
type="text"
v-model="form.VerifyValue"
auto-complete="off"
style="margin: 0 auto 8px auto"
placeholder="input your captcha numbers">
</el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
style="width:100%"
v-loading="loading"
@click.native.prevent="submitForm">
Verify Captcha
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
<el-tabs v-model="form.CaptchaType"
style="width: 70%;margin-left: auto;margin-right: auto;"
type="border-card" @tab-click="handleClick">
<el-tab-pane label="DriverDigit" name="digit">
<el-form
label-width="280px"
label-position="left">
<el-form-item label="DriverDigit.Length">
<el-slider v-model="form.DriverDigit.Length" :min="1" :max="10" show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverDigit.Width">
<el-slider v-model="form.DriverDigit.Width" :min="20" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverDigit.Height">
<el-slider v-model="form.DriverDigit.Height" :min="20" :max="180" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverDigit.MaxSkew">
<el-slider v-model="form.DriverDigit.MaxSkew" :step="0.05" :min="0.1" :max="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverDigit.DotCount">
<el-slider v-model="form.DriverDigit.DotCount" :min="2" :max="100" show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="DriverString" name="string">
<el-form
label-width="280px"
label-position="left">
<el-form-item label="DriverString.Length">
<el-slider v-model="form.DriverString.Length" :min="1" :max="10" show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverString.Source">
<el-input v-model.trim="form.DriverString.Source"
type="textarea"
placeholder="Any Unicode string is OK, Korean Japanese Greek Arabic Thai ..."
@change="generateCaptcha"></el-input>
</el-form-item>
<el-form-item label="DriverString.Width">
<el-slider v-model="form.DriverString.Width" :min="20" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverString.Height">
<el-slider v-model="form.DriverString.Height" :min="20" :max="180" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverString.NoiseCount">
<el-slider v-model="form.DriverString.NoiseCount" :min="0" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverString.ShowLineOptions">
<el-checkbox-group v-model="form.ShowLineOptions" @change="generateCaptcha">
<el-checkbox label="2">OptionShowHollowLine</el-checkbox>
<el-checkbox label="4">OptionShowSlimeLine</el-checkbox>
<el-checkbox label="8">OptionShowSineLine</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverString.Fonts">
<el-checkbox-group v-model="form.DriverString.Fonts" @change="generateCaptcha">
<el-checkbox v-for="f in fonts" :label="f" :key="f">{{f}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverString.BgColor.">
<el-form-item label="R">
<el-slider v-model="form.DriverString.BgColor.R" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="G">
<el-slider v-model="form.DriverString.BgColor.G" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="B">
<el-slider v-model="form.DriverString.BgColor.B" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="A">
<el-slider v-model="form.DriverString.BgColor.A" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="DriverMath" name="math">
<el-form
label-width="280px"
label-position="left">
<el-form-item label="DriverMath.Width">
<el-slider v-model="form.DriverMath.Width" :min="20" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverMath.Height">
<el-slider v-model="form.DriverMath.Height" :min="20" :max="180" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverMath.NoiseCount">
<el-slider v-model="form.DriverMath.NoiseCount" :min="0" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverMath.ShowLineOptions">
<el-checkbox-group v-model="form.ShowLineOptions" @change="generateCaptcha">
<el-checkbox label="2">OptionShowHollowLine</el-checkbox>
<el-checkbox label="4">OptionShowSlimeLine</el-checkbox>
<el-checkbox label="8">OptionShowSineLine</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverMath.Fonts">
<el-checkbox-group v-model="form.DriverMath.Fonts" @change="generateCaptcha">
<el-checkbox v-for="f in fonts" :label="f" :key="f">{{f}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverMath.BgColor.">
<el-form-item label="R">
<el-slider v-model="form.DriverMath.BgColor.R" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="G">
<el-slider v-model="form.DriverMath.BgColor.G" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="B">
<el-slider v-model="form.DriverMath.BgColor.B" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="A">
<el-slider v-model="form.DriverMath.BgColor.A" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="DriverChinese" name="chinese">
<el-form
label-width="280px"
label-position="left">
<el-form-item label="DriverChinese.Length">
<el-slider v-model="form.DriverChinese.Length" :min="1" :max="10" show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverChinese.Source">
<el-input v-model.trim="form.DriverChinese.Source"
type="textarea"
placeholder="可以是英文逗号分隔的词组"
@change="generateCaptcha"></el-input>
</el-form-item>
<el-form-item label="DriverChinese.Width">
<el-slider v-model="form.DriverChinese.Width" :min="20" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverChinese.Height">
<el-slider v-model="form.DriverChinese.Height" :min="20" :max="180" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverChinese.NoiseCount">
<el-slider v-model="form.DriverChinese.NoiseCount" :min="0" :max="480" :step="5"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverChinese.ShowLineOptions">
<el-checkbox-group v-model="form.ShowLineOptions" @change="generateCaptcha">
<el-checkbox label="2">OptionShowHollowLine</el-checkbox>
<el-checkbox label="4">OptionShowSlimeLine</el-checkbox>
<el-checkbox label="8">OptionShowSineLine</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverChinese.Fonts">
<el-checkbox-group v-model="form.DriverChinese.Fonts" @change="generateCaptcha">
<el-checkbox v-for="f in fonts" :label="f" :key="f">{{f}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="DriverChinese.BgColor.">
<el-form-item label="R">
<el-slider v-model="form.DriverChinese.BgColor.R" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="G">
<el-slider v-model="form.DriverChinese.BgColor.G" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="B">
<el-slider v-model="form.DriverChinese.BgColor.B" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="A">
<el-slider v-model="form.DriverChinese.BgColor.A" :min="0" :max="254" :step="1"
show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="DriverAudio" name="audio">
<el-form
label-width="280px"
label-position="left">
<el-form-item label="DriverAudio.Length">
<el-slider v-model="form.DriverAudio.Length" :min="1" :max="10" show-input
@change="generateCaptcha"></el-slider>
</el-form-item>
<el-form-item label="DriverAudio.Language">
<el-radio-group v-model="form.DriverAudio.Language" @change="generateCaptcha">
<el-radio-button label="en"></el-radio-button>
<el-radio-button label="zh"></el-radio-button>
<el-radio-button label="ru"></el-radio-button>
<el-radio-button label="ja"></el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-main>
<el-footer
style="line-height: 60px;margin-top: 1rem"
> https://github.com/dejavuzhou/felix
<a href="https://github.com/mojocn" type="success">https://github.com/mojocn</a>
<a href="https://mojotv.cn" type="success">Golang Tech Blog </a>
</el-footer>
</el-container>
</div>
</body>
<script>
new Vue({
el: '#app',
data: function () {
return {
fonts: [
"3Dumb.ttf",
"ApothecaryFont.ttf",
"Comismsh.ttf",
"DENNEthree-dee.ttf",
"DeborahFancyDress.ttf",
"Flim-Flam.ttf",
"RitaSmith.ttf",
"actionj.ttf",
"chromohv.ttf",
"wqy-microhei.ttc",
],
form: {
ShowLineOptions: [],
CaptchaType: "string",
Id: '',
VerifyValue: '',
DriverAudio: {
Length: 6,
Language: 'zh'
},
DriverString: {
Height: 60,
Width: 240,
ShowLineOptions: 0,
NoiseCount: 0,
Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
Length: 6,
Fonts: ["wqy-microhei.ttc"],
BgColor: {R: 0, G: 0, B: 0, A: 0},
},
DriverMath: {
Height: 60,
Width: 240,
ShowLineOptions: 0,
NoiseCount: 0,
Length: 6,
Fonts: ["wqy-microhei.ttc"],
BgColor: {R: 0, G: 0, B: 0, A: 0},
},
DriverChinese: {
Height: 60,
Width: 320,
ShowLineOptions: 0,
NoiseCount: 0,
Source: "设想,你在,处理,消费者,的音,频输,出音,频可,能无,论什,么都,没有,任何,输出,或者,它可,能是,单声道,立体声,或是,环绕立,体声的,,不想要,的值",
Length: 2,
Fonts: ["wqy-microhei.ttc"],
BgColor: {R: 125, G: 125, B: 0, A: 118},
},
DriverDigit: {
Height: 80,
Width: 240,
Length: 5,
MaxSkew: 0.7,
DotCount: 80
}
},
blob: "",
loading: false
}
},
computed: {
DriverStringSequencedCharacters: {
get: function () {
return this.form.DriverString.SequencedCharacters.join(',')
},
set: function (newValue) {
this.form.DriverString.SequencedCharacters = newValue.split(',')
}
}
},
methods: {
doShowLineOptions(val) {
if (val > 0) {
this.ShowLineOptions = this.form.ShowLineOptions | val;
} else {
}
},
formatTooltip: function (val) {
var items = ['CaptchaModeNumber', 'CaptchaModeAlphabet', 'CaptchaModeArithmetic',
'CaptchaModeNumberAlphabet', 'CaptchaModeChinese', 'CaptchaModeUseSequencedCharacters'];
return items[val];
},
handleClick: function (tab, event) {
this.generateCaptcha();
},
generateCaptcha: function () {
this.loading = true;
//generate uuid string so the serve can verify numbers in the png
//you can generate the captchaId in other way
var that = this;
var opt = 0;
this.form.ShowLineOptions.forEach(item => {
opt = opt | item;
});
this.form.DriverString.ShowLineOptions = opt;
this.form.DriverMath.ShowLineOptions = opt;
//this.form.DriverChineseWords.ShowLineOptions = opt;
// the api/getCaptcha endpoint only recieve captchaId paramenter
axios.post('/api/getCaptcha', that.form)
.then(function (response) {
that.loading = false;
that.form.Id = response.data.captchaId;
that.blob = response.data.data;
})
.catch(function (error) {
that.loading = false;
that.$notify({
title: 500,
message: 'net work or server error',
type: "error"
});
});
},
submitForm: function () {
var that = this;
this.loading = true;
axios.post('/api/verifyCaptcha', that.form)
.then(function (response) {
that.loading = false;
that.$notify({
title: response.data.msg,
message: response.data.data,
type: response.data.code
});
if (response.data.code === "success") {
that.generateCaptcha(false)
}
})
.catch(function (error) {
that.loading = false;
that.$notify({
title: 500,
message: 'net work or server error',
type: "error"
});
});
}
},
mounted: function () {
this.generateCaptcha()
}
})
</script>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

@ -0,0 +1,58 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package base64Captcha supports digits, numbers,alphabet, arithmetic, audio and digit-alphabet captcha.
// base64Captcha is used for fast development of RESTful APIs, web apps and backend services in Go. give a string identifier to the package and it returns with a base64-encoding-png-string
package base64Captcha
import (
"strings"
)
// Captcha captcha basic information.
type Captcha struct {
Driver Driver
Store Store
}
// NewCaptcha creates a captcha instance from driver and store
func NewCaptcha(driver Driver, store Store) *Captcha {
return &Captcha{Driver: driver, Store: store}
}
// Generate generates a random id, base64 image string or an error if any
func (c *Captcha) Generate() (id, b64s string, err error) {
id, content, answer := c.Driver.GenerateIdQuestionAnswer()
item, err := c.Driver.DrawCaptcha(content)
if err != nil {
return "", "", err
}
err = c.Store.Set(id, answer)
if err != nil {
return "", "", err
}
b64s = item.EncodeB64string()
return
}
// Verify by a given id key and remove the captcha value in store,
// return boolean value.
// if you has multiple captcha instances which share a same store.
// You may want to call `store.Verify` method instead.
func (c *Captcha) Verify(id, answer string, clear bool) (match bool) {
vv := c.Store.Get(id, clear)
//fix issue for some redis key-value string value
vv = strings.TrimSpace(vv)
return vv == strings.TrimSpace(answer)
}

@ -0,0 +1,138 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package base64Captcha supports digits, numbers,alphabet, arithmetic, audio and digit-alphabet captcha.
// base64Captcha is used for fast development of RESTful APIs, web apps and backend services in Go. give a string identifier to the package and it returns with a base64-encoding-png-string
package base64Captcha
import (
"math/rand"
"reflect"
"testing"
)
func TestCaptcha_GenerateB64s(t *testing.T) {
type fields struct {
Driver Driver
Store Store
}
dDigit := DriverDigit{80, 240, 5, 0.7, 5}
audioDriver := NewDriverAudio(rand.Intn(5), "en")
tests := []struct {
name string
fields fields
wantId string
wantB64s string
wantErr bool
}{
{"mem-digit", fields{&dDigit, DefaultMemStore}, "xxxx", "", false},
{"mem-audio", fields{audioDriver, DefaultMemStore}, "xxxx", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewCaptcha(tt.fields.Driver, tt.fields.Store)
gotId, b64s, err := c.Generate()
if (err != nil) != tt.wantErr {
t.Errorf("Captcha.Generate() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Log(b64s)
a := c.Store.Get(gotId, false)
if !c.Verify(gotId, a, true) {
t.Error("false")
}
})
}
}
func TestCaptcha_Verify(t *testing.T) {
type fields struct {
Driver Driver
Store Store
}
type args struct {
id string
answer string
clear bool
}
tests := []struct {
name string
fields fields
args args
wantMatch bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Captcha{
Driver: tt.fields.Driver,
Store: tt.fields.Store,
}
if gotMatch := c.Verify(tt.args.id, tt.args.answer, tt.args.clear); gotMatch != tt.wantMatch {
t.Errorf("Captcha.Verify() = %v, want %v", gotMatch, tt.wantMatch)
}
})
}
}
func TestNewCaptcha(t *testing.T) {
type args struct {
driver Driver
store Store
}
tests := []struct {
name string
args args
want *Captcha
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewCaptcha(tt.args.driver, tt.args.store); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewCaptcha() = %v, want %v", got, tt.want)
}
})
}
}
func TestCaptcha_Generate(t *testing.T) {
tests := []struct {
name string
c *Captcha
wantId string
wantB64s string
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotB64s, err := tt.c.Generate()
if (err != nil) != tt.wantErr {
t.Errorf("Captcha.Generate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotId != tt.wantId {
t.Errorf("Captcha.Generate() gotId = %v, want %v", gotId, tt.wantId)
}
if gotB64s != tt.wantB64s {
t.Errorf("Captcha.Generate() gotB64s = %v, want %v", gotB64s, tt.wantB64s)
}
})
}
}

@ -0,0 +1,60 @@
package base64Captcha
const idLen = 20
// idChars are characters allowed in captcha id.
var idChars = []byte(TxtNumbers + TxtAlphabet)
const (
imageStringDpi = 72.0
//TxtNumbers chacters for numbers.
TxtNumbers = "012346789"
//TxtAlphabet characters for alphabet.
TxtAlphabet = "ABCDEFGHJKMNOQRSTUVXYZabcdefghjkmnoqrstuvxyz"
//TxtSimpleCharaters simple numbers and alphabet
TxtSimpleCharaters = "13467ertyiadfhjkxcvbnERTYADFGHJKXCVBN"
//TxtChineseCharaters makes characters in chinese
TxtChineseCharaters = "的一是在不了有和人这中大为上个国我以要他" +
"时来用们生到作地于出就分对成会可主发年动" +
"同工也能下过子说产种面而方后多定行学法所" +
"民得经十三之进着等部度家电力里如水化高自" +
"二理起小物现实加量都两体制机当使点从业本" +
"去把性好应开它合还因由其些然前外天政四日" +
"那社义事平形相全表间样与关各重新线内数正" +
"心反你明看原又么利比或但质气第向道命此变" +
"条只没结解问意建月公无系军很情者最立代想" +
"已通并提直题党程展五果料象员革位入常文总" +
"次品式活设及管特件长求老头基资边流路级少" +
"图山统接知较将组见计别她手角期根论运农指" +
"几九区强放决西被干做必战先回则任取据处队" +
"南给色光门即保治北造百规热领七海口东导器" +
"压志世金增争济阶油思术极交受联什认六共权" +
"收证改清己美再采转更单风切打白教速花带安" +
"场身车例真务具万每目至达走积示议声报斗完" +
"类八离华名确才科张信马节话米整空元况今集" +
"温传土许步群广石记需段研界拉林律叫且究观" +
"越织装影算低持音众书布复容儿须际商非验连" +
"断深难近矿千周委素技备半办青省列习响约支" +
"般史感劳便团往酸历市克何除消构府称太准精" +
"值号率族维划选标写存候毛亲快效斯院查江型" +
"眼王按格养易置派层片始却专状育厂京识适属" +
"圆包火住调满县局照参红细引听该铁价严龙飞"
//MimeTypeAudio output base64 mine-type.
MimeTypeAudio = "audio/wav"
//MimeTypeImage output base64 mine-type.
MimeTypeImage = "image/png"
//Emoji is a source string for randTxt
Emoji = "😀😃💯😄🤖😻😅🤣😂🧑🙃😉😊😇😍👴🤩😘😗☺👽♀😙♂😋😛🎨😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐🙉😶😏💗🙄😬🤥😌😪🤤😷🤢🤮🤯😵🤠😎🧐😨😰😱😭😖😡🤬👿☠💀💥💢"
)
//var cjkFontFamilies = readCJKFonts()
const (
//OptionShowHollowLine shows hollow line
OptionShowHollowLine = 2
//OptionShowSlimeLine shows slime line
OptionShowSlimeLine = 4
//OptionShowSineLine shows sine line
OptionShowSineLine = 8
)

@ -0,0 +1,36 @@
// Copyright 2017 Eric Zhou. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package base64Captcha
//DriverAudio captcha config for captcha-engine-audio.
type DriverAudio struct {
// Length Default number of digits in captcha solution.
Length int
// Language possible values for lang are "en", "ja", "ru", "zh".
Language string
}
//DefaultDriverAudio is a default audio driver
var DefaultDriverAudio = NewDriverAudio(6, "en")
//NewDriverAudio creates a driver of audio
func NewDriverAudio(length int, language string) *DriverAudio {
return &DriverAudio{Length: length, Language: language}
}
//DrawCaptcha creates audio captcha item
func (d *DriverAudio) DrawCaptcha(content string) (item Item, err error) {
digits := stringToFakeByte(content)
audio := newAudio("", digits, d.Language)
return audio, nil
}
//GenerateIdQuestionAnswer creates id,captcha content and answer
func (d *DriverAudio) GenerateIdQuestionAnswer() (id, q, a string) {
id = RandomId()
digits := randomDigits(d.Length)
a = parseDigitsToString(digits)
return id, a, a
}

@ -0,0 +1,89 @@
// Copyright 2017 Eric Zhou. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package base64Captcha
import (
"reflect"
"testing"
)
func TestDriverAudio_DrawCaptcha(t *testing.T) {
type fields struct {
CaptchaLen int
Language string
}
type args struct {
content string
}
tests := []struct {
name string
fields fields
args args
wantItem Item
wantErr bool
}{
{"Audio", fields{4, "zh"}, args{"1234"}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DriverAudio{
Length: tt.fields.CaptchaLen,
Language: tt.fields.Language,
}
gotItem, err := d.DrawCaptcha(tt.args.content)
if err != nil {
t.Error(err)
}
itemWriteFile(gotItem, "_builds", tt.args.content, "wav")
})
}
}
func TestNewDriverAudio(t *testing.T) {
type args struct {
length int
language string
}
tests := []struct {
name string
args args
want *DriverAudio
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverAudio(tt.args.length, tt.args.language); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverAudio() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverAudio_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverAudio
wantId string
wantQ string
wantA string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotQ, gotA := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverAudio.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotQ != tt.wantQ {
t.Errorf("DriverAudio.GenerateIdQuestionAnswer() gotQ = %v, want %v", gotQ, tt.wantQ)
}
if gotA != tt.wantA {
t.Errorf("DriverAudio.GenerateIdQuestionAnswer() gotA = %v, want %v", gotA, tt.wantA)
}
})
}
}

@ -0,0 +1,146 @@
package base64Captcha
import (
"image/color"
"math/rand"
"strings"
"github.com/golang/freetype/truetype"
)
//DriverChinese is a driver of unicode Chinese characters.
type DriverChinese struct {
//Height png height in pixel.
Height int
//Width Captcha png width in pixel.
Width int
//NoiseCount text noise count.
NoiseCount int
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions int
//Length random string length.
Length int
//Source is a unicode which is the rand string from.
Source string
//BgColor captcha image background color (optional)
BgColor *color.RGBA
//fontsStorage font storage (optional)
fontsStorage FontsStorage
//Fonts loads by name see fonts.go's comment
Fonts []string
fontsArray []*truetype.Font
}
//NewDriverChinese creates a driver of Chinese characters
func NewDriverChinese(height int, width int, noiseCount int, showLineOptions int, length int, source string, bgColor *color.RGBA, fontsStorage FontsStorage, fonts []string) *DriverChinese {
if fontsStorage == nil {
fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range fonts {
tf := fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
return &DriverChinese{Height: height, Width: width, NoiseCount: noiseCount, ShowLineOptions: showLineOptions, Length: length, Source: source, BgColor: bgColor, fontsStorage: fontsStorage, fontsArray: tfs}
}
//ConvertFonts loads fonts by names
func (d *DriverChinese) ConvertFonts() *DriverChinese {
if d.fontsStorage == nil {
d.fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range d.Fonts {
tf := d.fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
d.fontsArray = tfs
return d
}
//GenerateIdQuestionAnswer generates captcha content and its answer
func (d *DriverChinese) GenerateIdQuestionAnswer() (id, content, answer string) {
id = RandomId()
ss := strings.Split(d.Source, ",")
length := len(ss)
if length == 1 {
c := RandText(d.Length, ss[0])
return id, c, c
}
if length <= d.Length {
c := RandText(d.Length, TxtNumbers+TxtAlphabet)
return id, c, c
}
res := make([]string, d.Length)
for k := range res {
res[k] = ss[rand.Intn(length)]
}
content = strings.Join(res, "")
return id, content, content
}
//DrawCaptcha generates captcha item(image)
func (d *DriverChinese) DrawCaptcha(content string) (item Item, err error) {
var bgc color.RGBA
if d.BgColor != nil {
bgc = *d.BgColor
} else {
bgc = RandLightColor()
}
itemChar := NewItemChar(d.Width, d.Height, bgc)
//draw hollow line
if d.ShowLineOptions&OptionShowHollowLine == OptionShowHollowLine {
itemChar.drawHollowLine()
}
//draw slime line
if d.ShowLineOptions&OptionShowSlimeLine == OptionShowSlimeLine {
itemChar.drawSlimLine(3)
}
//draw sine line
if d.ShowLineOptions&OptionShowSineLine == OptionShowSineLine {
itemChar.drawSineLine()
}
//draw noise
if d.NoiseCount > 0 {
source := TxtNumbers + TxtAlphabet + ",.[]<>"
noise := RandText(d.NoiseCount, strings.Repeat(source, d.NoiseCount))
err = itemChar.drawNoise(noise, d.fontsArray)
if err != nil {
return
}
}
//draw content
err = itemChar.drawText(content, d.fontsArray)
if err != nil {
return
}
return itemChar, nil
}

@ -0,0 +1,104 @@
package base64Captcha
import (
"image/color"
"reflect"
"testing"
)
func TestNewDriverChinese(t *testing.T) {
type args struct {
height int
width int
noiseCount int
showLineOptions int
length int
source string
bgColor *color.RGBA
fonts []string
}
tests := []struct {
name string
args args
want *DriverChinese
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverChinese(tt.args.height, tt.args.width, tt.args.noiseCount, tt.args.showLineOptions, tt.args.length, tt.args.source, tt.args.bgColor, nil, tt.args.fonts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverChinese() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverChinese_ConvertFonts(t *testing.T) {
tests := []struct {
name string
d *DriverChinese
want *DriverChinese
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.d.ConvertFonts(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DriverChinese.ConvertFonts() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverChinese_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverChinese
wantId string
wantContent string
wantAnswer string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotContent, gotAnswer := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverChinese.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotContent != tt.wantContent {
t.Errorf("DriverChinese.GenerateIdQuestionAnswer() gotContent = %v, want %v", gotContent, tt.wantContent)
}
if gotAnswer != tt.wantAnswer {
t.Errorf("DriverChinese.GenerateIdQuestionAnswer() gotAnswer = %v, want %v", gotAnswer, tt.wantAnswer)
}
})
}
}
func TestDriverChinese_DrawCaptcha(t *testing.T) {
type args struct {
content string
}
tests := []struct {
name string
d *DriverChinese
args args
wantItem Item
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotItem, err := tt.d.DrawCaptcha(tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("DriverChinese.DrawCaptcha() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotItem, tt.wantItem) {
t.Errorf("DriverChinese.DrawCaptcha() = %v, want %v", gotItem, tt.wantItem)
}
})
}
}

@ -0,0 +1,80 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package base64Captcha
import "math/rand"
//DriverDigit config for captcha-engine-digit.
type DriverDigit struct {
// Height png height in pixel.
Height int
// Width Captcha png width in pixel.
Width int
// DefaultLen Default number of digits in captcha solution.
Length int
// MaxSkew max absolute skew factor of a single digit.
MaxSkew float64
// DotCount Number of background circles.
DotCount int
}
//NewDriverDigit creates a driver of digit
func NewDriverDigit(height int, width int, length int, maxSkew float64, dotCount int) *DriverDigit {
return &DriverDigit{Height: height, Width: width, Length: length, MaxSkew: maxSkew, DotCount: dotCount}
}
//DefaultDriverDigit is a default driver of digit
var DefaultDriverDigit = NewDriverDigit(80, 240, 5, 0.7, 80)
//GenerateIdQuestionAnswer creates captcha content and answer
func (d *DriverDigit) GenerateIdQuestionAnswer() (id, q, a string) {
id = RandomId()
digits := randomDigits(d.Length)
a = parseDigitsToString(digits)
return id, a, a
}
//DrawCaptcha creates digit captcha item
func (d *DriverDigit) DrawCaptcha(content string) (item Item, err error) {
// Initialize PRNG.
itemDigit := NewItemDigit(d.Width, d.Height, d.DotCount, d.MaxSkew)
//parse digits to string
digits := stringToFakeByte(content)
itemDigit.calculateSizes(d.Width, d.Height, len(digits))
// Randomly position captcha inside the image.
maxx := d.Width - (itemDigit.width+itemDigit.dotSize)*len(digits) - itemDigit.dotSize
maxy := d.Height - itemDigit.height - itemDigit.dotSize*2
var border int
if d.Width > d.Height {
border = d.Height / 5
} else {
border = d.Width / 5
}
x := rand.Intn(maxx-border*2) + border
y := rand.Intn(maxy-border*2) + border
// Draw digits.
for _, n := range digits {
itemDigit.drawDigit(digitFontData[n], x, y)
x += itemDigit.width + itemDigit.dotSize
}
// Draw strike-through line.
itemDigit.strikeThrough()
// Apply wave distortion.
itemDigit.distort(rand.Float64()*(10-5)+5, rand.Float64()*(200-100)+100)
// Fill image with random circles.
itemDigit.fillWithCircles(d.DotCount, itemDigit.dotSize)
return itemDigit, nil
}

@ -0,0 +1,110 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package base64Captcha
import (
"reflect"
"testing"
)
func TestDriverDigit_DrawCaptcha(t *testing.T) {
type fields struct {
Height int
Width int
CaptchaLen int
MaxSkew float64
DotCount int
}
type args struct {
content string
}
tests := []struct {
name string
fields fields
args args
wantItem Item
wantErr bool
}{
{"Digit", fields{80, 240, 6, 0.6, 8}, args{RandText(6, TxtNumbers)}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DriverDigit{
Height: tt.fields.Height,
Width: tt.fields.Width,
Length: tt.fields.CaptchaLen,
MaxSkew: tt.fields.MaxSkew,
DotCount: tt.fields.DotCount,
}
gotItem, err := d.DrawCaptcha(tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("DriverDigit.DrawCaptcha() error = %v, wantErr %v", err, tt.wantErr)
return
}
itemWriteFile(gotItem, "_builds", tt.args.content, "png")
})
}
}
func TestNewDriverDigit(t *testing.T) {
type args struct {
height int
width int
length int
maxSkew float64
dotCount int
}
tests := []struct {
name string
args args
want *DriverDigit
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverDigit(tt.args.height, tt.args.width, tt.args.length, tt.args.maxSkew, tt.args.dotCount); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverDigit() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverDigit_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverDigit
wantId string
wantQ string
wantA string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotQ, gotA := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverDigit.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotQ != tt.wantQ {
t.Errorf("DriverDigit.GenerateIdQuestionAnswer() gotQ = %v, want %v", gotQ, tt.wantQ)
}
if gotA != tt.wantA {
t.Errorf("DriverDigit.GenerateIdQuestionAnswer() gotA = %v, want %v", gotA, tt.wantA)
}
})
}
}

@ -0,0 +1,123 @@
package base64Captcha
import (
"fmt"
"image/color"
"math/rand"
"github.com/golang/freetype/truetype"
)
//https://en.wikipedia.org/wiki/Unicode_block
var langMap = map[string][]int{
//"zh-CN": []int{19968, 40869},
"latin": {0x0000, 0x007f},
"zh": {0x4e00, 0x9fa5},
"ko": {12593, 12686},
"jp": {12449, 12531}, //[]int{12353, 12435}
"ru": {1025, 1169},
"th": {0x0e00, 0x0e7f},
"greek": {0x0380, 0x03ff},
"arabic": {0x0600, 0x06ff},
"hebrew": {0x0590, 0x05ff},
//"emotion": []int{0x1f601, 0x1f64f},
}
func generateRandomRune(size int, code string) string {
lang, ok := langMap[code]
if !ok {
fmt.Sprintf("can not font language of %s", code)
lang = langMap["latin"]
}
start := lang[0]
end := lang[1]
randRune := make([]rune, size)
for i := range randRune {
idx := rand.Intn(end-start) + start
randRune[i] = rune(idx)
}
return string(randRune)
}
//DriverLanguage generates language unicode by lanuage
type DriverLanguage struct {
// Height png height in pixel.
Height int
// Width Captcha png width in pixel.
Width int
//NoiseCount text noise count.
NoiseCount int
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions int
//Length random string length.
Length int
//BgColor captcha image background color (optional)
BgColor *color.RGBA
//fontsStorage font storage (optional)
fontsStorage FontsStorage
//Fonts loads by name see fonts.go's comment
Fonts []*truetype.Font
LanguageCode string
}
//NewDriverLanguage creates a driver
func NewDriverLanguage(height int, width int, noiseCount int, showLineOptions int, length int, bgColor *color.RGBA, fontsStorage FontsStorage, fonts []*truetype.Font, languageCode string) *DriverLanguage {
return &DriverLanguage{Height: height, Width: width, NoiseCount: noiseCount, ShowLineOptions: showLineOptions, Length: length, BgColor: bgColor, fontsStorage: fontsStorage, Fonts: fonts, LanguageCode: languageCode}
}
//GenerateIdQuestionAnswer creates content and answer
func (d *DriverLanguage) GenerateIdQuestionAnswer() (id, content, answer string) {
id = RandomId()
content = generateRandomRune(d.Length, d.LanguageCode)
return id, content, content
}
//DrawCaptcha creates item
func (d *DriverLanguage) DrawCaptcha(content string) (item Item, err error) {
var bgc color.RGBA
if d.BgColor != nil {
bgc = *d.BgColor
} else {
bgc = RandLightColor()
}
itemChar := NewItemChar(d.Width, d.Height, bgc)
//draw hollow line
if d.ShowLineOptions&OptionShowHollowLine == OptionShowHollowLine {
itemChar.drawHollowLine()
}
//draw slime line
if d.ShowLineOptions&OptionShowSlimeLine == OptionShowSlimeLine {
itemChar.drawSlimLine(3)
}
//draw sine line
if d.ShowLineOptions&OptionShowSineLine == OptionShowSineLine {
itemChar.drawSineLine()
}
//draw noise
if d.NoiseCount > 0 {
noise := RandText(d.NoiseCount, TxtNumbers+TxtAlphabet+",.[]<>")
err = itemChar.drawNoise(noise, fontsAll)
if err != nil {
return
}
}
//draw content
//use font that match your language
err = itemChar.drawText(content, []*truetype.Font{fontChinese})
if err != nil {
return
}
return itemChar, nil
}

@ -0,0 +1,96 @@
package base64Captcha
import (
"image/color"
"reflect"
"testing"
"github.com/golang/freetype/truetype"
)
func TestDriverLanguage_DrawCaptcha(t *testing.T) {
ds := NewDriverLanguage(80, 240, 5, OptionShowSineLine|OptionShowSlimeLine|OptionShowHollowLine, 5, nil, nil, []*truetype.Font{fontChinese}, "emotion")
for i := 0; i < 40; i++ {
_, q, _ := ds.GenerateIdQuestionAnswer()
item, err := ds.DrawCaptcha(q)
if err != nil {
t.Error(err)
}
itemWriteFile(item, "_builds", RandomId(), "png")
}
}
func Test_generateRandomRune(t *testing.T) {
type args struct {
size int
code string
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := generateRandomRune(tt.args.size, tt.args.code); got != tt.want {
t.Errorf("generateRandomRune() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewDriverLanguage(t *testing.T) {
type args struct {
height int
width int
noiseCount int
showLineOptions int
length int
bgColor *color.RGBA
fonts []*truetype.Font
languageCode string
}
tests := []struct {
name string
args args
want *DriverLanguage
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverLanguage(tt.args.height, tt.args.width, tt.args.noiseCount, tt.args.showLineOptions, tt.args.length, tt.args.bgColor, nil, tt.args.fonts, tt.args.languageCode); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverLanguage() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverLanguage_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverLanguage
wantId string
wantContent string
wantAnswer string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotContent, gotAnswer := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverLanguage.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotContent != tt.wantContent {
t.Errorf("DriverLanguage.GenerateIdQuestionAnswer() gotContent = %v, want %v", gotContent, tt.wantContent)
}
if gotAnswer != tt.wantAnswer {
t.Errorf("DriverLanguage.GenerateIdQuestionAnswer() gotAnswer = %v, want %v", gotAnswer, tt.wantAnswer)
}
})
}
}

@ -0,0 +1,143 @@
package base64Captcha
import (
"fmt"
"image/color"
"math/rand"
"strings"
"github.com/golang/freetype/truetype"
)
//DriverMath captcha config for captcha math
type DriverMath struct {
//Height png height in pixel.
Height int
// Width Captcha png width in pixel.
Width int
//NoiseCount text noise count.
NoiseCount int
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions int
//BgColor captcha image background color (optional)
BgColor *color.RGBA
//fontsStorage font storage (optional)
fontsStorage FontsStorage
//Fonts loads by name see fonts.go's comment
Fonts []string
fontsArray []*truetype.Font
}
//NewDriverMath creates a driver of math
func NewDriverMath(height int, width int, noiseCount int, showLineOptions int, bgColor *color.RGBA, fontsStorage FontsStorage, fonts []string) *DriverMath {
if fontsStorage == nil {
fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range fonts {
tf := fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
return &DriverMath{Height: height, Width: width, NoiseCount: noiseCount, ShowLineOptions: showLineOptions, fontsArray: tfs, BgColor: bgColor, Fonts: fonts}
}
//ConvertFonts loads fonts from names
func (d *DriverMath) ConvertFonts() *DriverMath {
if d.fontsStorage == nil {
d.fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range d.Fonts {
tf := d.fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
d.fontsArray = tfs
return d
}
//GenerateIdQuestionAnswer creates id,captcha content and answer
func (d *DriverMath) GenerateIdQuestionAnswer() (id, question, answer string) {
id = RandomId()
operators := []string{"+", "-", "x"}
var mathResult int32
switch operators[rand.Int31n(3)] {
case "+":
a := rand.Int31n(20)
b := rand.Int31n(20)
question = fmt.Sprintf("%d+%d=?", a, b)
mathResult = a + b
case "x":
a := rand.Int31n(10)
b := rand.Int31n(10)
question = fmt.Sprintf("%dx%d=?", a, b)
mathResult = a * b
default:
a := rand.Int31n(80) + rand.Int31n(20)
b := rand.Int31n(80)
question = fmt.Sprintf("%d-%d=?", a, b)
mathResult = a - b
}
answer = fmt.Sprintf("%d", mathResult)
return
}
//DrawCaptcha creates math captcha item
func (d *DriverMath) DrawCaptcha(question string) (item Item, err error) {
var bgc color.RGBA
if d.BgColor != nil {
bgc = *d.BgColor
} else {
bgc = RandLightColor()
}
itemChar := NewItemChar(d.Width, d.Height, bgc)
//波浪线 比较丑
if d.ShowLineOptions&OptionShowHollowLine == OptionShowHollowLine {
itemChar.drawHollowLine()
}
//背景有文字干扰
if d.NoiseCount > 0 {
noise := RandText(d.NoiseCount, strings.Repeat(TxtNumbers, d.NoiseCount))
err = itemChar.drawNoise(noise, fontsAll)
if err != nil {
return
}
}
//画 细直线 (n 条)
if d.ShowLineOptions&OptionShowSlimeLine == OptionShowSlimeLine {
itemChar.drawSlimLine(3)
}
//画 多个小波浪线
if d.ShowLineOptions&OptionShowSineLine == OptionShowSineLine {
itemChar.drawSineLine()
}
//draw question
err = itemChar.drawText(question, d.fontsArray)
if err != nil {
return
}
return itemChar, nil
}

@ -0,0 +1,124 @@
package base64Captcha
import (
"image/color"
"reflect"
"testing"
)
func TestDriverMath_DrawCaptcha(t *testing.T) {
type fields struct {
Height int
Width int
NoiseCount int
ShowLineOptions int
BgColor *color.RGBA
Fonts []string
}
type args struct {
question string
}
tests := []struct {
name string
fields fields
args args
wantItem Item
wantErr bool
}{
{"Math",
fields{80, 240, 5, OptionShowSineLine | OptionShowSlimeLine | OptionShowHollowLine, nil, []string{"3Dumb.ttf"}},
args{""},
nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DriverMath{
Height: tt.fields.Height,
Width: tt.fields.Width,
NoiseCount: tt.fields.NoiseCount,
ShowLineOptions: tt.fields.ShowLineOptions,
BgColor: tt.fields.BgColor,
fontsStorage: DefaultEmbeddedFonts,
Fonts: tt.fields.Fonts,
}
d.ConvertFonts()
_, q, a := d.GenerateIdQuestionAnswer()
gotItem, err := d.DrawCaptcha(q)
if (err != nil) != tt.wantErr {
t.Errorf("DriverMath.DrawCaptcha() error = %v, wantErr %v", err, tt.wantErr)
return
}
itemWriteFile(gotItem, "_builds", a, "png")
})
}
}
func TestNewDriverMath(t *testing.T) {
type args struct {
height int
width int
noiseCount int
showLineOptions int
bgColor *color.RGBA
fonts []string
}
tests := []struct {
name string
args args
want *DriverMath
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverMath(tt.args.height, tt.args.width, tt.args.noiseCount, tt.args.showLineOptions, tt.args.bgColor, nil, tt.args.fonts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverMath() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverMath_ConvertFonts(t *testing.T) {
tests := []struct {
name string
d *DriverMath
want *DriverMath
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.d.ConvertFonts(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DriverMath.ConvertFonts() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverMath_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverMath
wantId string
wantQuestion string
wantAnswer string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotQuestion, gotAnswer := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverMath.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotQuestion != tt.wantQuestion {
t.Errorf("DriverMath.GenerateIdQuestionAnswer() gotQuestion = %v, want %v", gotQuestion, tt.wantQuestion)
}
if gotAnswer != tt.wantAnswer {
t.Errorf("DriverMath.GenerateIdQuestionAnswer() gotAnswer = %v, want %v", gotAnswer, tt.wantAnswer)
}
})
}
}

@ -0,0 +1,130 @@
package base64Captcha
import (
"image/color"
"strings"
"github.com/golang/freetype/truetype"
)
//DriverChar captcha config for captcha-engine-characters.
type DriverString struct {
// Height png height in pixel.
Height int
// Width Captcha png width in pixel.
Width int
//NoiseCount text noise count.
NoiseCount int
//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
ShowLineOptions int
//Length random string length.
Length int
//Source is a unicode which is the rand string from.
Source string
//BgColor captcha image background color (optional)
BgColor *color.RGBA
//fontsStorage font storage (optional)
fontsStorage FontsStorage
//Fonts loads by name see fonts.go's comment
Fonts []string
fontsArray []*truetype.Font
}
//NewDriverString creates driver
func NewDriverString(height int, width int, noiseCount int, showLineOptions int, length int, source string, bgColor *color.RGBA, fontsStorage FontsStorage, fonts []string) *DriverString {
if fontsStorage == nil {
fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range fonts {
tf := fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
return &DriverString{Height: height, Width: width, NoiseCount: noiseCount, ShowLineOptions: showLineOptions, Length: length, Source: source, BgColor: bgColor, fontsStorage: fontsStorage, fontsArray: tfs, Fonts: fonts}
}
//ConvertFonts loads fonts by names
func (d *DriverString) ConvertFonts() *DriverString {
if d.fontsStorage == nil {
d.fontsStorage = DefaultEmbeddedFonts
}
tfs := []*truetype.Font{}
for _, fff := range d.Fonts {
tf := d.fontsStorage.LoadFontByName("fonts/" + fff)
tfs = append(tfs, tf)
}
if len(tfs) == 0 {
tfs = fontsAll
}
d.fontsArray = tfs
return d
}
//GenerateIdQuestionAnswer creates id,content and answer
func (d *DriverString) GenerateIdQuestionAnswer() (id, content, answer string) {
id = RandomId()
content = RandText(d.Length, d.Source)
return id, content, content
}
//DrawCaptcha draws captcha item
func (d *DriverString) DrawCaptcha(content string) (item Item, err error) {
var bgc color.RGBA
if d.BgColor != nil {
bgc = *d.BgColor
} else {
bgc = RandLightColor()
}
itemChar := NewItemChar(d.Width, d.Height, bgc)
//draw hollow line
if d.ShowLineOptions&OptionShowHollowLine == OptionShowHollowLine {
itemChar.drawHollowLine()
}
//draw slime line
if d.ShowLineOptions&OptionShowSlimeLine == OptionShowSlimeLine {
itemChar.drawSlimLine(3)
}
//draw sine line
if d.ShowLineOptions&OptionShowSineLine == OptionShowSineLine {
itemChar.drawSineLine()
}
//draw noise
if d.NoiseCount > 0 {
source := TxtNumbers + TxtAlphabet + ",.[]<>"
noise := RandText(d.NoiseCount, strings.Repeat(source, d.NoiseCount))
err = itemChar.drawNoise(noise, d.fontsArray)
if err != nil {
return
}
}
//draw content
err = itemChar.drawText(content, d.fontsArray)
if err != nil {
return
}
return itemChar, nil
}

@ -0,0 +1,147 @@
package base64Captcha
import (
"image/color"
"reflect"
"testing"
"github.com/golang/freetype/truetype"
)
func TestDriverString_ConvertFonts2(t *testing.T) {
d := &DriverString{
Height: 80,
Width: 240,
NoiseCount: 20,
ShowLineOptions: 100,
Length: 5,
BgColor: nil,
fontsArray: fontsAll,
}
gotItem, err := d.DrawCaptcha("45Ad8")
if err != nil {
t.Error(err)
return
}
err = itemWriteFile(gotItem, "_builds", "abc", "png")
if err != nil {
t.Error(err)
}
}
func TestDriverString_DrawCaptcha(t *testing.T) {
type fields struct {
Height int
Width int
NoiseTextCount int
NoiseDotCount int
ShowNoiseOption int
CaptchaLen int
BgColor *color.RGBA
Fonts []*truetype.Font
}
type args struct {
content string
}
tests := []struct {
name string
fields fields
args args
wantItem Item
wantErr bool
}{
{"string", fields{80, 240, 20, 100, 2, 5, nil, fontsAll}, args{"45Ad8"}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DriverString{
Height: tt.fields.Height,
Width: tt.fields.Width,
NoiseCount: tt.fields.NoiseTextCount,
ShowLineOptions: tt.fields.ShowNoiseOption,
Length: tt.fields.CaptchaLen,
BgColor: tt.fields.BgColor,
fontsArray: tt.fields.Fonts,
}
gotItem, err := d.DrawCaptcha(tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("DriverString.DrawCaptcha() error = %v, wantErr %v", err, tt.wantErr)
return
}
err = itemWriteFile(gotItem, "_builds", tt.args.content, "png")
if err != nil {
t.Error(err)
}
})
}
}
func TestNewDriverString(t *testing.T) {
type args struct {
height int
width int
noiseCount int
showLineOptions int
length int
source string
bgColor *color.RGBA
fonts []string
}
tests := []struct {
name string
args args
want *DriverString
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewDriverString(tt.args.height, tt.args.width, tt.args.noiseCount, tt.args.showLineOptions, tt.args.length, tt.args.source, tt.args.bgColor, nil, tt.args.fonts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewDriverString() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverString_ConvertFonts(t *testing.T) {
tests := []struct {
name string
d *DriverString
want *DriverString
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.d.ConvertFonts(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DriverString.ConvertFonts() = %v, want %v", got, tt.want)
}
})
}
}
func TestDriverString_GenerateIdQuestionAnswer(t *testing.T) {
tests := []struct {
name string
d *DriverString
wantId string
wantContent string
wantAnswer string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotId, gotContent, gotAnswer := tt.d.GenerateIdQuestionAnswer()
if gotId != tt.wantId {
t.Errorf("DriverString.GenerateIdQuestionAnswer() gotId = %v, want %v", gotId, tt.wantId)
}
if gotContent != tt.wantContent {
t.Errorf("DriverString.GenerateIdQuestionAnswer() gotContent = %v, want %v", gotContent, tt.wantContent)
}
if gotAnswer != tt.wantAnswer {
t.Errorf("DriverString.GenerateIdQuestionAnswer() gotAnswer = %v, want %v", gotAnswer, tt.wantAnswer)
}
})
}
}

@ -0,0 +1,27 @@
package base64Captcha
import "testing"
func TestHandlerCaptchaGenerate(t *testing.T) {
s := DefaultMemStore
driver := &DriverString{
Height: 80,
Width: 240,
NoiseCount: 10,
ShowLineOptions: 10,
Length: 10,
Source: "axclajsdlfkjalskjdglasdg",
BgColor: nil,
Fonts: nil,
}
c := NewCaptcha(driver, s)
id, _, err := c.Generate()
if err != nil {
t.Fatalf("some error: %s", err)
}
t.Logf("id: %s", id)
}

@ -0,0 +1,239 @@
package base64Captcha
import (
"math/rand"
"github.com/golang/freetype/truetype"
)
var fontsSimple = DefaultEmbeddedFonts.LoadFontsByNames([]string{
"fonts/3Dumb.ttf",
"fonts/ApothecaryFont.ttf",
"fonts/Comismsh.ttf",
"fonts/DENNEthree-dee.ttf",
"fonts/DeborahFancyDress.ttf",
"fonts/Flim-Flam.ttf",
"fonts/RitaSmith.ttf",
"fonts/actionj.ttf",
"fonts/chromohv.ttf",
})
//var fontemoji = loadFontByName("fonts/seguiemj.ttf")
var fontsAll = append(fontsSimple, fontChinese)
var fontChinese = DefaultEmbeddedFonts.LoadFontByName("fonts/wqy-microhei.ttc")
//randFontFrom choose random font family.选择随机的字体
func randFontFrom(fonts []*truetype.Font) *truetype.Font {
fontCount := len(fonts)
if fontCount == 0 {
//loading default fonts
fonts = fontsAll
fontCount = len(fontsAll)
}
index := rand.Intn(fontCount)
return fonts[index]
}
var digitFontData = [][]byte{
{ // 0
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 1
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 2
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 3
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 4
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
},
{ // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 6
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 7
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
},
{ // 8
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 9
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
},
}

Binary file not shown.

@ -0,0 +1,7 @@
go get -u github.com/jteeuwen/go-bindata/...
go-bindata fonts
sed -i "s/package main/package base64Captcha/g" bindata.go
https://github.com/jteeuwen/go-bindata

@ -0,0 +1,44 @@
package base64Captcha
import (
"embed"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
type EmbeddedFontsStorage struct {
fs embed.FS
}
func (s *EmbeddedFontsStorage) LoadFontByName(name string) *truetype.Font {
fontBytes, err := s.fs.ReadFile(name)
if err != nil {
panic(err)
}
//font file bytes to trueTypeFont
trueTypeFont, err := freetype.ParseFont(fontBytes)
if err != nil {
panic(err)
}
return trueTypeFont
}
// LoadFontsByNames import fonts from dir.
// make the simple-font(RitaSmith.ttf) the first font of trueTypeFonts.
func (s *EmbeddedFontsStorage) LoadFontsByNames(assetFontNames []string) []*truetype.Font {
fonts := make([]*truetype.Font, 0)
for _, assetName := range assetFontNames {
f := s.LoadFontByName(assetName)
fonts = append(fonts, f)
}
return fonts
}
func NewEmbeddedFontsStorage(fs embed.FS) *EmbeddedFontsStorage {
return &EmbeddedFontsStorage{
fs: fs,
}
}

@ -0,0 +1,10 @@
package base64Captcha
import "embed"
//go:embed fonts/*.ttf
//go:embed fonts/*.ttc
// defaultEmbeddedFontsFS Built-in font storage.
var defaultEmbeddedFontsFS embed.FS
var DefaultEmbeddedFonts = NewEmbeddedFontsStorage(defaultEmbeddedFontsFS)

@ -0,0 +1,52 @@
package base64Captcha
import (
"testing"
)
// sources:
// fonts/3Dumb.ttf (142.224kB)
// fonts/ApothecaryFont.ttf (62.08kB)
// fonts/Comismsh.ttf (80.132kB)
// fonts/DENNEthree-dee.ttf (83.188kB)
// fonts/DeborahFancyDress.ttf (32.52kB)
// fonts/Flim-Flam.ttf (140.576kB)
// fonts/RitaSmith.ttf (31.24kB)
// fonts/actionj.ttf (34.944kB)
// fonts/chromohv.ttf (45.9kB)
// fonts/readme.md (162B)
// fonts/wqy-microhei.ttc (5.177MB)
func Test_loadFontByName(t *testing.T) {
f := DefaultEmbeddedFonts.LoadFontByName("fonts/wqy-microhei.ttc")
if f == nil {
t.Error("failed")
}
defer recoverPanic(t)
f = DefaultEmbeddedFonts.LoadFontByName("fonts/readme.md")
}
func recoverPanic(t *testing.T) {
r := recover()
if r == nil {
t.Error("not trigger panic")
}
}
func Test_loadFontsByNames(t *testing.T) {
fs := DefaultEmbeddedFonts.LoadFontsByNames([]string{"fonts/chromohv.ttf", "fonts/RitaSmith.ttf"})
if len(fs) != 2 {
t.Error("failed")
}
defer recoverPanic(t)
DefaultEmbeddedFonts.LoadFontsByNames([]string{"fonts/actionj.txxxxxtf"})
}
func Test_randFontFrom(t *testing.T) {
f := randFontFrom(fontsAll)
if f == nil {
t.Error("failed")
}
}

@ -0,0 +1,9 @@
package base64Captcha
// Driver captcha interface for captcha engine to to write staff
type Driver interface {
//DrawCaptcha draws binary item
DrawCaptcha(content string) (item Item, err error)
//GenerateIdQuestionAnswer creates rand id, content and answer
GenerateIdQuestionAnswer() (id, q, a string)
}

@ -0,0 +1,12 @@
package base64Captcha
import "github.com/golang/freetype/truetype"
// FontsStorage interface for working with fonts
type FontsStorage interface {
// LoadFontByName returns the font from the storage
LoadFontByName(name string) *truetype.Font
// LoadFontsByNames returns multiple fonts from storage
LoadFontsByNames(assetFontNames []string) []*truetype.Font
}

@ -0,0 +1,11 @@
package base64Captcha
import "io"
//Item is captcha item interface
type Item interface {
//WriteTo writes to a writer
WriteTo(w io.Writer) (n int64, err error)
//EncodeB64string encodes as base64 string
EncodeB64string() string
}

@ -0,0 +1,20 @@
package base64Captcha
// Store An object implementing Store interface can be registered with SetCustomStore
// function to handle storage and retrieval of captcha ids and solutions for
// them, replacing the default memory store.
//
// It is the responsibility of an object to delete expired and used captchas
// when necessary (for example, the default memory store collects them in Set
// method after the certain amount of captchas has been stored.)
type Store interface {
// Set sets the digits for the captcha id.
Set(id string, value string) error
// Get returns stored digits for the captcha id. Clear indicates
// whether the captcha must be deleted from the store.
Get(id string, clear bool) string
//Verify captcha's answer directly
Verify(id, answer string, clear bool) bool
}

@ -0,0 +1,169 @@
// Copyright 2017 Eric Zhou. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package base64Captcha
import (
"bytes"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"math/rand"
)
//ItemAudio captcha-audio-engine return type.
type ItemAudio struct {
answer string
body *bytes.Buffer
digitSounds [][]byte
//rng siprng
}
// newAudio returns a new audio captcha with the given digits, where each digit
// must be in range 0-9. Digits are pronounced in the given language. If there
// are no sounds for the given language, English is used.
// Possible values for lang are "en", "ja", "ru", "zh".
func newAudio(id string, digits []byte, lang string) *ItemAudio {
a := new(ItemAudio)
if sounds, ok := digitSounds[lang]; ok {
a.digitSounds = sounds
} else {
a.digitSounds = digitSounds["en"]
}
numsnd := make([][]byte, len(digits))
for i, n := range digits {
snd := a.randomizedDigitSound(n)
setSoundLevel(snd, 1.5)
numsnd[i] = snd
}
// Random intervals between digits (including beginning).
intervals := make([]int, len(digits)+1)
intdur := 0
for i := range intervals {
dur := randIntRange(sampleRate, sampleRate*2) // 1 to 2 seconds
intdur += dur
intervals[i] = dur
}
// Generate background sound.
bg := a.makeBackgroundSound(a.longestDigitSndLen()*len(digits) + intdur)
// Create buffer and write audio to it.
sil := makeSilence(sampleRate / 5)
bufcap := 3*len(beepSound) + 2*len(sil) + len(bg) + len(endingBeepSound)
a.body = bytes.NewBuffer(make([]byte, 0, bufcap))
// Write prelude, three beeps.
a.body.Write(beepSound)
a.body.Write(sil)
a.body.Write(beepSound)
a.body.Write(sil)
a.body.Write(beepSound)
// Write digits.
pos := intervals[0]
for i, v := range numsnd {
mixSound(bg[pos:], v)
pos += len(v) + intervals[i+1]
}
a.body.Write(bg)
// Write ending (one beep).
a.body.Write(endingBeepSound)
return a
}
// encodedLen returns the length of WAV-encoded audio captcha.
func (a *ItemAudio) encodedLen() int {
return len(waveHeader) + 4 + a.body.Len()
}
func (a *ItemAudio) makeBackgroundSound(length int) []byte {
b := a.makeWhiteNoise(length, 4)
for i := 0; i < length/(sampleRate/10); i++ {
snd := reversedSound(a.digitSounds[rand.Intn(10)])
//snd = changeSpeed(snd, a.rng.Float(0.8, 1.2))
place := rand.Intn(len(b) - len(snd))
setSoundLevel(snd, randFloat64Range(0.04, 0.08))
mixSound(b[place:], snd)
}
return b
}
func (a *ItemAudio) randomizedDigitSound(n byte) []byte {
s := a.randomSpeed(a.digitSounds[n])
setSoundLevel(s, randFloat64Range(0.85, 1.2))
return s
}
func (a *ItemAudio) longestDigitSndLen() int {
n := 0
for _, v := range a.digitSounds {
if n < len(v) {
n = len(v)
}
}
return n
}
func (a *ItemAudio) randomSpeed(b []byte) []byte {
pitch := randFloat64Range(0.95, 1.1)
return changeSpeed(b, pitch)
}
func (a *ItemAudio) makeWhiteNoise(length int, level uint8) []byte {
noise := randBytes(length)
adj := 128 - level/2
for i, v := range noise {
v %= level
v += adj
noise[i] = v
}
return noise
}
// WriteTo writes captcha audio in WAVE format into the given io.Writer, and
// returns the number of bytes written and an error if any.
func (a *ItemAudio) WriteTo(w io.Writer) (n int64, err error) {
// Calculate padded length of PCM chunk data.
bodyLen := uint32(a.body.Len())
paddedBodyLen := bodyLen
if bodyLen%2 != 0 {
paddedBodyLen++
}
totalLen := uint32(len(waveHeader)) - 4 + paddedBodyLen
// Header.
header := make([]byte, len(waveHeader)+4) // includes 4 bytes for chunk size
copy(header, waveHeader)
// Put the length of whole RIFF chunk.
binary.LittleEndian.PutUint32(header[4:], totalLen)
// Put the length of WAVE chunk.
binary.LittleEndian.PutUint32(header[len(waveHeader):], bodyLen)
// Write header.
nn, err := w.Write(header)
n = int64(nn)
if err != nil {
return
}
// Write data.
n, err = a.body.WriteTo(w)
n += int64(nn)
if err != nil {
return
}
// Pad byte if chunk length is odd.
// (As header has even length, we can check if n is odd, not chunk).
if bodyLen != paddedBodyLen {
w.Write([]byte{0})
n++
}
return
}
// EncodeB64string encodes a sound to base64 string
func (a *ItemAudio) EncodeB64string() string {
var buf bytes.Buffer
if _, err := a.WriteTo(&buf); err != nil {
panic(err)
}
return fmt.Sprintf("data:%s;base64,%s", MimeTypeAudio, base64.StdEncoding.EncodeToString(buf.Bytes()))
}

@ -0,0 +1,122 @@
// Copyright 2017 Eric Zhou. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package base64Captcha
import (
"bytes"
"fmt"
"testing"
"time"
)
func Test_newAudio(t *testing.T) {
type args struct {
id string
digits []byte
lang string
}
tests := []struct {
name string
args args
want *ItemAudio
}{
{"zh3", args{RandomId(), randomDigits(3), "zh"}, nil},
{"en4", args{RandomId(), randomDigits(4), "en"}, nil},
{"ru2", args{RandomId(), randomDigits(2), "ru"}, nil},
{"jp5", args{RandomId(), randomDigits(5), "jp"}, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := newAudio(tt.args.id, tt.args.digits, tt.args.lang)
if got == nil {
t.Errorf("newAudio() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemAudio_encodedLen(t *testing.T) {
ia := newAudio(RandomId(), randomDigits(3), "zh")
tests := []struct {
name string
a *ItemAudio
want int
}{
{"encode", ia, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.encodedLen(); got < tt.want {
t.Errorf("ItemAudio.encodedLen() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemAudio_longestDigitSndLen(t *testing.T) {
baseS := "0123456789abcdef"
base := int64(len(baseS))
num := time.Now().UnixNano()
fmt.Printf("%x\n", num)
newB := []byte{}
for {
idx := num % base
bbb := []byte{byte(baseS[idx])}
newB = append(bbb, newB...)
num = num / base
if num == 0 {
break
}
}
t.Log(string(newB))
}
func TestItemAudio_WriteTo(t *testing.T) {
ia := newAudio(RandomId(), randomDigits(3), "zh")
tests := []struct {
name string
a *ItemAudio
wantN int64
wantW string
wantErr bool
}{
{"one", ia, 0, "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
gotN, err := tt.a.WriteTo(w)
if (err != nil) != tt.wantErr {
t.Errorf("ItemAudio.WriteTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotN < 1 {
t.Errorf("ItemAudio.WriteTo() = %v, want %v", gotN, tt.wantN)
}
if gotW := w.String(); len(gotW) < 1 {
t.Errorf("ItemAudio.WriteTo() = %v, want %v", gotW, tt.wantW)
}
})
}
}
func TestItemAudio_EncodeB64string(t *testing.T) {
ia := newAudio(RandomId(), randomDigits(5), "en")
tests := []struct {
name string
a *ItemAudio
want string
}{
{"b64", ia, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.EncodeB64string(); len(got) < 1 {
t.Errorf("ItemAudio.EncodeB64string() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,258 @@
package base64Captcha
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"image"
"image/color"
"image/draw"
"image/png"
"io"
"log"
"math"
"math/rand"
)
//ItemChar captcha item of unicode characters
type ItemChar struct {
bgColor color.Color
width int
height int
nrgba *image.NRGBA
}
//NewItemChar creates a captcha item of characters
func NewItemChar(w int, h int, bgColor color.RGBA) *ItemChar {
d := ItemChar{width: w, height: h}
m := image.NewNRGBA(image.Rect(0, 0, w, h))
draw.Draw(m, m.Bounds(), &image.Uniform{bgColor}, image.ZP, draw.Src)
d.nrgba = m
return &d
}
//drawHollowLine draw strong and bold white line.
func (item *ItemChar) drawHollowLine() *ItemChar {
first := item.width / 20
end := first * 19
lineColor := RandLightColor()
x1 := float64(rand.Intn(first))
//y1 := float64(rand.Intn(y)+y);
x2 := float64(rand.Intn(first) + end)
multiple := float64(rand.Intn(5)+3) / float64(5)
if int(multiple*10)%3 == 0 {
multiple = multiple * -1.0
}
w := item.height / 20
for ; x1 < x2; x1++ {
y := math.Sin(x1*math.Pi*multiple/float64(item.width)) * float64(item.height/3)
if multiple < 0 {
y = y + float64(item.height/2)
}
item.nrgba.Set(int(x1), int(y), lineColor)
for i := 0; i <= w; i++ {
item.nrgba.Set(int(x1), int(y)+i, lineColor)
}
}
return item
}
//drawSineLine draw a sine line.
func (item *ItemChar) drawSineLine() *ItemChar {
var py float64
//振幅
a := rand.Intn(item.height / 2)
//Y轴方向偏移量
b := random(int64(-item.height/4), int64(item.height/4))
//X轴方向偏移量
f := random(int64(-item.height/4), int64(item.height/4))
// 周期
var t float64
if item.height > item.width/2 {
t = random(int64(item.width/2), int64(item.height))
} else if item.height == item.width/2 {
t = float64(item.height)
} else {
t = random(int64(item.height), int64(item.width/2))
}
w := float64((2 * math.Pi) / t)
// 曲线横坐标起始位置
px1 := 0
px2 := int(random(int64(float64(item.width)*0.8), int64(item.width)))
c := RandDeepColor()
for px := px1; px < px2; px++ {
if w != 0 {
py = float64(a)*math.Sin(w*float64(px)+f) + b + (float64(item.width) / float64(5))
i := item.height / 5
for i > 0 {
item.nrgba.Set(px+i, int(py), c)
//fmt.Println(px + i,int(py) )
i--
}
}
}
return item
}
//drawSlimLine draw n slim-random-color lines.
func (item *ItemChar) drawSlimLine(num int) *ItemChar {
first := item.width / 10
end := first * 9
y := item.height / 3
for i := 0; i < num; i++ {
point1 := point{X: rand.Intn(first), Y: rand.Intn(y)}
point2 := point{X: rand.Intn(first) + end, Y: rand.Intn(y)}
if i%2 == 0 {
point1.Y = rand.Intn(y) + y*2
point2.Y = rand.Intn(y)
} else {
point1.Y = rand.Intn(y) + y*(i%2)
point2.Y = rand.Intn(y) + y*2
}
item.drawBeeline(point1, point2, RandDeepColor())
}
return item
}
func (item *ItemChar) drawBeeline(point1 point, point2 point, lineColor color.RGBA) {
dx := math.Abs(float64(point1.X - point2.X))
dy := math.Abs(float64(point2.Y - point1.Y))
sx, sy := 1, 1
if point1.X >= point2.X {
sx = -1
}
if point1.Y >= point2.Y {
sy = -1
}
err := dx - dy
for {
item.nrgba.Set(point1.X, point1.Y, lineColor)
item.nrgba.Set(point1.X+1, point1.Y, lineColor)
item.nrgba.Set(point1.X-1, point1.Y, lineColor)
item.nrgba.Set(point1.X+2, point1.Y, lineColor)
item.nrgba.Set(point1.X-2, point1.Y, lineColor)
if point1.X == point2.X && point1.Y == point2.Y {
return
}
e2 := err * 2
if e2 > -dy {
err -= dy
point1.X += sx
}
if e2 < dx {
err += dx
point1.Y += sy
}
}
}
func (item *ItemChar) drawNoise(noiseText string, fonts []*truetype.Font) error {
c := freetype.NewContext()
c.SetDPI(imageStringDpi)
c.SetClip(item.nrgba.Bounds())
c.SetDst(item.nrgba)
c.SetHinting(font.HintingFull)
rawFontSize := float64(item.height) / (1 + float64(rand.Intn(7))/float64(10))
for _, char := range noiseText {
rw := rand.Intn(item.width)
rh := rand.Intn(item.height)
fontSize := rawFontSize/2 + float64(rand.Intn(5))
c.SetSrc(image.NewUniform(RandLightColor()))
c.SetFontSize(fontSize)
c.SetFont(randFontFrom(fonts))
pt := freetype.Pt(rw, rh)
if _, err := c.DrawString(string(char), pt); err != nil {
log.Println(err)
}
}
return nil
}
//drawText draw captcha string to image.把文字写入图像验证码
func (item *ItemChar) drawText(text string, fonts []*truetype.Font) error {
c := freetype.NewContext()
c.SetDPI(imageStringDpi)
c.SetClip(item.nrgba.Bounds())
c.SetDst(item.nrgba)
c.SetHinting(font.HintingFull)
if len(text) == 0 {
return errors.New("text must not be empty, there is nothing to draw")
}
fontWidth := item.width / len(text)
for i, s := range text {
fontSize := item.height * (rand.Intn(7) + 7) / 16
c.SetSrc(image.NewUniform(RandDeepColor()))
c.SetFontSize(float64(fontSize))
c.SetFont(randFontFrom(fonts))
x := fontWidth*i + fontWidth/fontSize
y := item.height/2 + fontSize/2 - rand.Intn(item.height/16*3)
pt := freetype.Pt(x, y)
if _, err := c.DrawString(string(s), pt); err != nil {
return err
}
}
return nil
}
//BinaryEncoding encodes an image to PNG and returns a byte slice.
func (item *ItemChar) BinaryEncoding() []byte {
var buf bytes.Buffer
if err := png.Encode(&buf, item.nrgba); err != nil {
panic(err.Error())
}
return buf.Bytes()
}
// WriteTo writes captcha character in png format into the given io.Writer, and
// returns the number of bytes written and an error if any.
func (item *ItemChar) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(item.BinaryEncoding())
return int64(n), err
}
// EncodeB64string encodes an image to base64 string
func (item *ItemChar) EncodeB64string() string {
return fmt.Sprintf("data:%s;base64,%s", MimeTypeImage, base64.StdEncoding.EncodeToString(item.BinaryEncoding()))
}
type point struct {
X int
Y int
}

@ -0,0 +1,213 @@
package base64Captcha
import (
"bytes"
"image/color"
"reflect"
"testing"
"github.com/golang/freetype/truetype"
)
func TestNewItemChar(t *testing.T) {
type args struct {
w int
h int
bgColor color.RGBA
}
tests := []struct {
name string
args args
want *ItemChar
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewItemChar(tt.args.w, tt.args.h, tt.args.bgColor); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewItemChar() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemChar_drawHollowLine(t *testing.T) {
tests := []struct {
name string
item *ItemChar
want *ItemChar
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.item.drawHollowLine(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ItemChar.drawHollowLine() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemChar_drawSineLine(t *testing.T) {
tests := []struct {
name string
item *ItemChar
want *ItemChar
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.item.drawSineLine(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ItemChar.drawSineLine() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemChar_drawSlimLine(t *testing.T) {
type args struct {
num int
}
tests := []struct {
name string
item *ItemChar
args args
want *ItemChar
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.item.drawSlimLine(tt.args.num); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ItemChar.drawSlimLine() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemChar_drawBeeline(t *testing.T) {
type args struct {
point1 point
point2 point
lineColor color.RGBA
}
tests := []struct {
name string
item *ItemChar
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.item.drawBeeline(tt.args.point1, tt.args.point2, tt.args.lineColor)
})
}
}
func TestItemChar_drawNoise(t *testing.T) {
type args struct {
noiseText string
fonts []*truetype.Font
}
tests := []struct {
name string
item *ItemChar
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.item.drawNoise(tt.args.noiseText, tt.args.fonts); (err != nil) != tt.wantErr {
t.Errorf("ItemChar.drawNoise() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestItemChar_drawText(t *testing.T) {
type args struct {
text string
fonts []*truetype.Font
}
tests := []struct {
name string
item *ItemChar
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.item.drawText(tt.args.text, tt.args.fonts); (err != nil) != tt.wantErr {
t.Errorf("ItemChar.drawText() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestItemChar_BinaryEncoding(t *testing.T) {
tests := []struct {
name string
item *ItemChar
want []byte
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.item.BinaryEncoding(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ItemChar.BinaryEncoding() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemChar_WriteTo(t *testing.T) {
tests := []struct {
name string
item *ItemChar
want int64
wantW string
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
got, err := tt.item.WriteTo(w)
if (err != nil) != tt.wantErr {
t.Errorf("ItemChar.WriteTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ItemChar.WriteTo() = %v, want %v", got, tt.want)
}
if gotW := w.String(); gotW != tt.wantW {
t.Errorf("ItemChar.WriteTo() = %v, want %v", gotW, tt.wantW)
}
})
}
}
func TestItemChar_EncodeB64string(t *testing.T) {
tests := []struct {
name string
item *ItemChar
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.item.EncodeB64string(); got != tt.want {
t.Errorf("ItemChar.EncodeB64string() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,258 @@
package base64Captcha
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/png"
"io"
"math"
"math/rand"
)
const (
digitFontWidth = 11
digitFontHeight = 18
digitFontBlackChar = 1
)
// ItemDigit digits captcha Struct
type ItemDigit struct {
width int
height int
*image.Paletted
dotSize int
dotCount int
maxSkew float64
//rng siprng
}
//NewItemDigit create a instance of item-digit
func NewItemDigit(width int, height int, dotCount int, maxSkew float64) *ItemDigit {
itemDigit := &ItemDigit{width: width, height: height, dotCount: dotCount, maxSkew: maxSkew}
//init image.Paletted
itemDigit.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), createRandPaletteColors(dotCount))
return itemDigit
}
func createRandPaletteColors(dotCount int) color.Palette {
p := make([]color.Color, dotCount+1)
// Transparent color.
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
// Primary color.
prim := color.RGBA{
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
0xFF,
}
if dotCount == 0 {
p[0] = prim
return p
}
p[1] = prim
// Circle colors.
for i := 2; i <= dotCount; i++ {
p[i] = randomBrightness(prim, 255)
}
return p
}
func (m *ItemDigit) calculateSizes(width, height, ncount int) {
// Goal: fit all digits inside the image.
var border int
if width > height {
border = height / 4
} else {
border = width / 4
}
// Convert everything to floats for calculations.
w := float64(width - border*2)
h := float64(height - border*2)
// fw takes into account 1-dot spacing between digits.
fw := float64(digitFontWidth + 1)
fh := float64(digitFontHeight)
nc := float64(ncount)
// Calculate the width of a single digit taking into account only the
// width of the image.
nw := w / nc
// Calculate the height of a digit from this width.
nh := nw * fh / fw
// Digit too high?
if nh > h {
// Fit digits based on height.
nh = h
nw = fw / fh * nh
}
// Calculate dot size.
m.dotSize = int(nh / fh)
if m.dotSize < 1 {
m.dotSize = 1
}
// Save everything, making the actual width smaller by 1 dot to account
// for spacing between digits.
m.width = int(nw) - m.dotSize
m.height = int(nh)
}
func (m *ItemDigit) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
for x := fromX; x <= toX; x++ {
m.SetColorIndex(x, y, colorIdx)
}
}
func (m *ItemDigit) drawCircle(x, y, radius int, colorIdx uint8) {
f := 1 - radius
dfx := 1
dfy := -2 * radius
xo := 0
yo := radius
m.SetColorIndex(x, y+radius, colorIdx)
m.SetColorIndex(x, y-radius, colorIdx)
m.drawHorizLine(x-radius, x+radius, y, colorIdx)
for xo < yo {
if f >= 0 {
yo--
dfy += 2
f += dfy
}
xo++
dfx += 2
f += dfx
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
}
}
func (m *ItemDigit) fillWithCircles(n, maxradius int) {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
for i := 0; i < n; i++ {
//colorIdx := uint8(m.rng.Int(1, m.dotCount-1))
colorIdx := uint8(randIntRange(1, m.dotCount-1))
//r := m.rng.Int(1, maxradius)
r := randIntRange(1, maxradius)
//m.drawCircle(m.rng.Int(r, maxx-r), m.rng.Int(r, maxy-r), r, colorIdx)
m.drawCircle(randIntRange(r, maxx-r), randIntRange(r, maxy-r), r, colorIdx)
}
}
func (m *ItemDigit) strikeThrough() {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
y := randIntRange(maxy/3, maxy-maxy/3)
amplitude := randFloat64Range(5, 20)
period := randFloat64Range(80, 180)
dx := 2.0 * math.Pi / period
for x := 0; x < maxx; x++ {
xo := amplitude * math.Cos(float64(y)*dx)
yo := amplitude * math.Sin(float64(x)*dx)
for yn := 0; yn < m.dotSize; yn++ {
//r := m.rng.Int(0, m.dotSize)
r := rand.Intn(m.dotSize)
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
}
}
}
//draw digit
func (m *ItemDigit) drawDigit(digit []byte, x, y int) {
skf := randFloat64Range(-m.maxSkew, m.maxSkew)
xs := float64(x)
r := m.dotSize / 2
y += randIntRange(-r, r)
for yo := 0; yo < digitFontHeight; yo++ {
for xo := 0; xo < digitFontWidth; xo++ {
if digit[yo*digitFontWidth+xo] != digitFontBlackChar {
continue
}
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
}
xs += skf
x = int(xs)
}
}
func (m *ItemDigit) distort(amplude float64, period float64) {
w := m.Bounds().Max.X
h := m.Bounds().Max.Y
oldm := m.Paletted
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
dx := 2.0 * 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)
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
}
}
m.Paletted = newm
}
func randomBrightness(c color.RGBA, max uint8) color.RGBA {
minc := min3(c.R, c.G, c.B)
maxc := max3(c.R, c.G, c.B)
if maxc > max {
return c
}
n := rand.Intn(int(max-maxc)) - int(minc)
return color.RGBA{
uint8(int(c.R) + n),
uint8(int(c.G) + n),
uint8(int(c.B) + n),
uint8(c.A),
}
}
func min3(x, y, z uint8) (m uint8) {
m = x
if y < m {
m = y
}
if z < m {
m = z
}
return
}
func max3(x, y, z uint8) (m uint8) {
m = x
if y > m {
m = y
}
if z > m {
m = z
}
return
}
// EncodeBinary encodes an image to PNG and returns a byte slice.
func (m *ItemDigit) EncodeBinary() []byte {
var buf bytes.Buffer
if err := png.Encode(&buf, m.Paletted); err != nil {
panic(err.Error())
}
return buf.Bytes()
}
// WriteTo writes captcha character in png format into the given io.Writer, and
// returns the number of bytes written and an error if any.
func (m *ItemDigit) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(m.EncodeBinary())
return int64(n), err
}
// EncodeB64string encodes an image to base64 string
func (m *ItemDigit) EncodeB64string() string {
return fmt.Sprintf("data:%s;base64,%s", MimeTypeImage, base64.StdEncoding.EncodeToString(m.EncodeBinary()))
}

@ -0,0 +1,102 @@
package base64Captcha
import (
"bytes"
"testing"
)
func TestNewItemDigit(t *testing.T) {
type args struct {
width int
height int
dotCount int
maxSkew float64
}
tests := []struct {
name string
args args
want *ItemDigit
}{
{"one", args{240, 80, 6, 0.8}, nil},
{"one", args{240, 80, 5, 0.8}, nil},
{"one", args{240, 80, 6, 0.8}, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewItemDigit(tt.args.width, tt.args.height, tt.args.dotCount, tt.args.maxSkew); got == nil {
t.Errorf("NewItemDigit() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemDigit_EncodeBinary(t *testing.T) {
idd := NewItemDigit(80, 300, 20, 0.25)
tests := []struct {
name string
m *ItemDigit
want []byte
}{
{"one", idd, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.m.EncodeBinary(); len(got) == 0 {
t.Errorf("ItemDigit.EncodeBinary() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemDigit_WriteTo(t *testing.T) {
tests := []struct {
name string
m *ItemDigit
want int64
wantW string
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
got, err := tt.m.WriteTo(w)
if (err != nil) != tt.wantErr {
t.Errorf("ItemDigit.WriteTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ItemDigit.WriteTo() = %v, want %v", got, tt.want)
}
if gotW := w.String(); gotW != tt.wantW {
t.Errorf("ItemDigit.WriteTo() = %v, want %v", gotW, tt.wantW)
}
})
}
}
func TestItemDigit_EncodeB64string(t *testing.T) {
idd := NewItemDigit(80, 300, 20, 0.25)
tests := []struct {
name string
m *ItemDigit
want string
}{
{"", idd, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.m.EncodeB64string(); got == tt.want {
t.Errorf("ItemDigit.EncodeB64string() = %v, want %v", got, tt.want)
}
})
}
}
func TestItemDigit_DotCountZero(t *testing.T) {
_ = NewItemDigit(80, 300, 0, 0.25)
}

@ -0,0 +1,115 @@
package base64Captcha
import (
"encoding/binary"
"image/color"
"math"
"math/rand"
"strings"
"time"
)
func init() {
//init rand seed
rand.Seed(time.Now().UnixNano())
}
//RandText creates random text of given size.
func RandText(size int, sourceChars string) string {
if sourceChars == "" || size == 0 {
return ""
}
if size >= len(sourceChars) {
sourceChars = strings.Repeat(sourceChars, size)
}
sourceRunes := []rune(sourceChars)
sourceLength := len(sourceRunes)
text := make([]rune, size)
for i := range text {
text[i] = sourceRunes[rand.Intn(sourceLength)]
}
return string(text)
}
//Random get random number between min and max. 生成指定大小的随机数.
func random(min int64, max int64) float64 {
return float64(min) + rand.Float64()*float64(max-min)
}
//RandDeepColor get random deep color. 随机生成深色系.
func RandDeepColor() color.RGBA {
randColor := RandColor()
increase := float64(30 + rand.Intn(255))
red := math.Abs(math.Min(float64(randColor.R)-increase, 255))
green := math.Abs(math.Min(float64(randColor.G)-increase, 255))
blue := math.Abs(math.Min(float64(randColor.B)-increase, 255))
return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: uint8(255)}
}
//RandLightColor get random ligth color. 随机生成浅色.
func RandLightColor() color.RGBA {
red := rand.Intn(55) + 200
green := rand.Intn(55) + 200
blue := rand.Intn(55) + 200
return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: uint8(255)}
}
//RandColor get random color. 生成随机颜色.
func RandColor() color.RGBA {
red := rand.Intn(255)
green := rand.Intn(255)
var blue int
if (red + green) > 400 {
blue = 0
} else {
blue = 400 - green - red
}
if blue > 255 {
blue = 255
}
return color.RGBA{R: uint8(red), G: uint8(green), B: uint8(blue), A: uint8(255)}
}
func randIntRange(from, to int) int {
// rand.Intn panics if n <= 0.
if to-from <= 0 {
return from
}
return rand.Intn(to-from) + from
}
func randFloat64Range(from, to float64) float64 {
return rand.Float64()*(to-from) + from
}
func randBytes(n int) []byte {
// Since we don't have a buffer for generated bytes in siprng state,
// we just generate enough 8-byte blocks and then cut the result to the
// required length. Doing it this way, we lose generated bytes, and we
// don't get the strictly sequential deterministic output from PRNG:
// calling Uint64() and then Bytes(3) produces different output than
// when calling them in the reverse order, but for our applications
// this is OK.
numBlocks := (n + 8 - 1) / 8
b := make([]byte, numBlocks*8)
for i := 0; i < len(b); i += 8 {
binary.LittleEndian.PutUint64(b[i:], rand.Uint64())
}
return b[:n]
}
// RandomId returns a new random id key string.
func RandomId() string {
b := randomBytesMod(idLen, byte(len(idChars)))
for i, c := range b {
b[i] = idChars[c]
}
return string(b)
}

@ -0,0 +1,183 @@
package base64Captcha
import (
"bytes"
"testing"
)
func TestRandText(t *testing.T) {
type args struct {
size int
sourceChars string
}
tests := []struct {
name string
args args
want string
}{
{"", args{0, "foo"}, ""},
{"", args{1, "aaa"}, "a"},
{"", args{2, "bbb"}, "bb"},
{"", args{3, "bbb"}, "bbb"},
{"", args{3, "b"}, "bbb"},
{"", args{4, "b"}, "bbbb"},
{"", args{4, ""}, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RandText(tt.args.size, tt.args.sourceChars); got != tt.want {
t.Errorf("RandText() = %v, want %v", got, tt.want)
}
})
}
}
func TestRandom(t *testing.T) {
type args struct {
min int64
max int64
}
tests := []struct {
name string
args args
}{
{"", args{-10, 10}},
{"", args{-1, 5}},
{"", args{0, 15}},
{"", args{10, 14}},
{"", args{10, 10}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := random(tt.args.min, tt.args.max)
// if out of bound then error
if got < float64(tt.args.min) || got > float64(tt.args.max) {
t.Errorf("RandText() = %v, out of range", got)
}
})
}
}
func TestRandDarkAndLightColor(t *testing.T) {
// Test RandColor
for i := 0; i < 100; i++ {
rgbA := RandColor()
if rgbA.R < 0 || rgbA.R > 255 ||
rgbA.B < 0 || rgbA.B > 255 ||
rgbA.G < 0 || rgbA.G > 255 {
t.Errorf("RandText() = %v, out of range", rgbA)
}
}
// Test RandLightColor
for i := 0; i < 100; i++ {
rgbA := RandLightColor()
if rgbA.R < 200 || rgbA.R > 255 ||
rgbA.B < 200 || rgbA.B > 255 ||
rgbA.G < 200 || rgbA.G > 255 {
t.Errorf("RandText() = %v, out of range", rgbA)
}
}
// Test RandDeepColor
for i := 0; i < 100; i++ {
rgbA := RandDeepColor()
if rgbA.R < 0 || rgbA.R > 255 ||
rgbA.B < 0 || rgbA.B > 255 ||
rgbA.G < 0 || rgbA.G > 255 {
t.Errorf("RandText() = %v, out of range", rgbA)
}
}
}
func TestRand(t *testing.T) {
// test rand int Range
type args struct {
from int
to int
}
tests := []struct {
name string
args args
}{
{"", args{-10, 10}},
{"", args{-1, 5}},
{"", args{0, 15}},
{"", args{10, 14}},
{"", args{10, 10}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := randIntRange(tt.args.from, tt.args.to)
// if out of bound then error
if got < tt.args.from || got > tt.args.to {
t.Errorf("RandText() = %v, out of range", got)
}
})
}
// test rand float Range
type fargs struct {
from float64
to float64
}
tests2 := []struct {
name string
arg fargs
}{
{"", fargs{-10.0, 10.1}},
{"", fargs{-1.0, 5.2}},
{"", fargs{0, 15.3}},
{"", fargs{10.1, 14.3}},
{"", fargs{10.5, 10.5}},
}
for _, tt := range tests2 {
t.Run(tt.name, func(t *testing.T) {
got := randFloat64Range(tt.arg.from, tt.arg.to)
// if out of bound then error
if got < tt.arg.from || got > tt.arg.to {
t.Errorf("RandText() = %v, out of range", got)
}
})
}
}
func TestRandomID(t *testing.T) {
id := RandomId()
if len(id) != idLen {
t.Errorf("Wrong length got %d, want %d", len(id), idLen)
}
for _, val := range id {
if !bytes.ContainsRune(idChars, val) {
t.Errorf("got %v, want %v", idChars, val)
}
}
}
func TestRandBytes(t *testing.T) {
type args struct {
n int
}
tests := []struct {
name string
args args
want int
}{
{"", args{5}, 5},
{"", args{0}, 0},
{"", args{1}, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := randBytes(tt.args.n)
// if out of bound then error
if len(got) != tt.want {
t.Errorf("randBytes() = %v, want %v", got, tt.want)
}
})
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,116 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package base64Captcha
import (
"container/list"
"sync"
"time"
)
// expValue stores timestamp and id of captchas. It is used in the list inside
// memoryStore for indexing generated captchas by timestamp to enable garbage
// collection of expired captchas.
type idByTimeValue struct {
timestamp time.Time
id string
}
// memoryStore is an internal store for captcha ids and their values.
type memoryStore struct {
sync.RWMutex
digitsById map[string]string
idByTime *list.List
// Number of items stored since last collection.
numStored int
// Number of saved items that triggers collection.
collectNum int
// Expiration time of captchas.
expiration time.Duration
}
// NewMemoryStore returns a new standard memory store for captchas with the
// given collection threshold and expiration time (duration). The returned
// store must be registered with SetCustomStore to replace the default one.
func NewMemoryStore(collectNum int, expiration time.Duration) Store {
s := new(memoryStore)
s.digitsById = make(map[string]string)
s.idByTime = list.New()
s.collectNum = collectNum
s.expiration = expiration
return s
}
func (s *memoryStore) Set(id string, value string) error {
s.Lock()
s.digitsById[id] = value
s.idByTime.PushBack(idByTimeValue{time.Now(), id})
s.numStored++
s.Unlock()
if s.numStored > s.collectNum {
go s.collect()
}
return nil
}
func (s *memoryStore) Verify(id, answer string, clear bool) bool {
v := s.Get(id, clear)
return v == answer
}
func (s *memoryStore) Get(id string, clear bool) (value string) {
if !clear {
// When we don't need to clear captcha, acquire read lock.
s.RLock()
defer s.RUnlock()
} else {
s.Lock()
defer s.Unlock()
}
value, ok := s.digitsById[id]
if !ok {
return
}
if clear {
delete(s.digitsById, id)
}
return
}
func (s *memoryStore) collect() {
now := time.Now()
s.Lock()
defer s.Unlock()
for e := s.idByTime.Front(); e != nil; {
e = s.collectOne(e, now)
}
}
func (s *memoryStore) collectOne(e *list.Element, specifyTime time.Time) *list.Element {
ev, ok := e.Value.(idByTimeValue)
if !ok {
return nil
}
if ev.timestamp.Add(s.expiration).Before(specifyTime) {
delete(s.digitsById, ev.id)
next := e.Next()
s.idByTime.Remove(e)
s.numStored--
return next
}
return nil
}

@ -0,0 +1,12 @@
package base64Captcha
import "time"
var (
// GCLimitNumber The number of captchas created that triggers garbage collection used by default store.
GCLimitNumber = 10240
// Expiration time of captchas used by default store.
Expiration = 10 * time.Minute
// DefaultMemStore is a shared storage for captchas, generated by New function.
DefaultMemStore = NewMemoryStore(GCLimitNumber, Expiration)
)

@ -0,0 +1,164 @@
// Copyright 2017 Eric Zhou. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package base64Captcha
import (
"fmt"
"math/rand"
"testing"
"time"
)
func TestSetGet(t *testing.T) {
s := NewMemoryStore(GCLimitNumber, Expiration)
id := "captcha id"
d := "random-string"
_ = s.Set(id, d)
d2 := s.Get(id, false)
if d2 != d {
t.Errorf("saved %v, getDigits returned got %v", d, d2)
}
}
func TestGetClear(t *testing.T) {
s := NewMemoryStore(GCLimitNumber, Expiration)
id := "captcha id"
d := "932839jfffjkdss"
_ = s.Set(id, d)
d2 := s.Get(id, true)
if d != d2 {
t.Errorf("saved %v, getDigitsClear returned got %v", d, d2)
}
d2 = s.Get(id, false)
if d2 != "" {
t.Errorf("getDigitClear didn't clear (%q=%v)", id, d2)
}
}
func BenchmarkSetCollect(b *testing.B) {
b.StopTimer()
d := "fdskfew9832232r"
s := NewMemoryStore(9999, -1)
ids := make([]string, 1000)
for i := range ids {
ids[i] = fmt.Sprintf("%d", rand.Int63())
}
b.StartTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
_ = s.Set(ids[j], d)
}
}
}
func TestMemoryStore_SetGoCollect(t *testing.T) {
s := NewMemoryStore(10, -1)
for i := 0; i <= 100; i++ {
_ = s.Set(fmt.Sprint(i), fmt.Sprint(i))
}
}
func TestMemoryStore_CollectNotExpire(t *testing.T) {
s := NewMemoryStore(10, time.Hour)
for i := 0; i < 50; i++ {
_ = s.Set(fmt.Sprint(i), fmt.Sprint(i))
}
// let background goroutine to go
time.Sleep(time.Second)
if v := s.Get("0", false); v != "0" {
t.Error("mem store get failed")
}
}
func TestNewMemoryStore(t *testing.T) {
type args struct {
collectNum int
expiration time.Duration
}
tests := []struct {
name string
args args
want Store
}{
{"", args{20, time.Hour}, nil},
{"", args{20, time.Hour * 5}, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewMemoryStore(tt.args.collectNum, tt.args.expiration); got == nil {
t.Errorf("NewMemoryStore() = %v, want %v", got, tt.want)
}
})
}
}
func Test_memoryStore_Set(t *testing.T) {
thisStore := NewMemoryStore(10, time.Hour)
type args struct {
id string
value string
}
tests := []struct {
name string
s Store
args args
}{
{"", thisStore, args{RandomId(), RandomId()}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = tt.s.Set(tt.args.id, tt.args.value)
})
}
}
func Test_memoryStore_Verify(t *testing.T) {
thisStore := NewMemoryStore(10, time.Hour)
_ = thisStore.Set("xx", "xx")
got := thisStore.Verify("xx", "xx", false)
if !got {
t.Error("failed1")
}
got = thisStore.Verify("xx", "xx", true)
if !got {
t.Error("failed2")
}
got = thisStore.Verify("xx", "xx", true)
if got {
t.Error("failed3")
}
}
func Test_memoryStore_Get(t *testing.T) {
thisStore := NewMemoryStore(10, time.Hour)
_ = thisStore.Set("xx", "xx")
got := thisStore.Get("xx", false)
if got != "xx" {
t.Error("failed1")
}
got = thisStore.Get("xx", true)
if got != "xx" {
t.Error("failed2")
}
got = thisStore.Get("xx", false)
if got == "xx" {
t.Error("failed3")
}
}

@ -0,0 +1,63 @@
package base64Captcha
import (
"sync"
"time"
)
//StoreSyncMap use sync.Map as store
type StoreSyncMap struct {
liveTime time.Duration
m *sync.Map
}
//NewStoreSyncMap new a instance
func NewStoreSyncMap(liveTime time.Duration) *StoreSyncMap {
return &StoreSyncMap{liveTime: liveTime, m: new(sync.Map)}
}
//smv a value type
type smv struct {
t time.Time
Value string
}
//newSmv create a instance
func newSmv(v string) *smv {
return &smv{t: time.Now(), Value: v}
}
//rmExpire remove expired items
func (s StoreSyncMap) rmExpire() {
expireTime := time.Now().Add(-s.liveTime)
s.m.Range(func(key, value interface{}) bool {
if sv, ok := value.(*smv); ok && sv.t.Before(expireTime) {
s.m.Delete(key)
}
return true
})
}
//Get get a string value
func (s StoreSyncMap) Set(id string, value string) {
s.rmExpire()
s.m.Store(id, newSmv(value))
}
//Set a string value
func (s StoreSyncMap) Get(id string, clear bool) string {
v, ok := s.m.Load(id)
if !ok {
return ""
}
s.m.Delete(id)
if sv, ok := v.(*smv); ok {
return sv.Value
}
return ""
}
//Verify check a string value
func (s StoreSyncMap) Verify(id, answer string, clear bool) bool {
return s.Get(id, clear) == answer
}

@ -0,0 +1,161 @@
package base64Captcha
import (
"reflect"
"sync"
"testing"
"time"
)
var tstore = NewStoreSyncMap(liveTime)
var liveTime = time.Second * 2
func TestNewStoreSyncMap(t *testing.T) {
type args struct {
liveTime time.Duration
}
tests := []struct {
name string
args args
want *StoreSyncMap
}{
{"new", args{liveTime}, tstore},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewStoreSyncMap(tt.args.liveTime); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewStoreSyncMap() = %v, want %v", got, tt.want)
}
})
}
}
func TestStoreSyncMap_Get(t *testing.T) {
tstore.Set("1", "1")
tstore.Set("2", "2")
type fields struct {
liveTime time.Duration
m *sync.Map
}
type args struct {
id string
clear bool
}
tests := []struct {
name string
fields fields
args args
want string
}{
{"get", fields{liveTime, tstore.m}, args{"1", false}, "1"},
{"get", fields{liveTime, tstore.m}, args{"2", true}, "2"},
{"get", fields{liveTime, tstore.m}, args{"2", true}, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StoreSyncMap{
liveTime: tt.fields.liveTime,
m: tt.fields.m,
}
if got := s.Get(tt.args.id, tt.args.clear); got != tt.want {
t.Errorf("Get() = %v, want %v", got, tt.want)
}
})
}
}
func TestStoreSyncMap_Expire(t *testing.T) {
tstore.Set("2", "22")
if v := tstore.Get("2", false); v != "22" {
t.Error("failed")
}
time.Sleep(time.Second * 2)
if v := tstore.Get("2", false); v != "" {
t.Error("expire failed")
}
}
func TestStoreSyncMap_Set(t *testing.T) {
type fields struct {
liveTime time.Duration
m *sync.Map
}
type args struct {
id string
value string
}
tests := []struct {
name string
fields fields
args args
}{
{"get", fields{liveTime, tstore.m}, args{"1", "1"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StoreSyncMap{
liveTime: tt.fields.liveTime,
m: tt.fields.m,
}
s.Set(tt.args.id, tt.args.value)
})
}
}
func TestStoreSyncMap_Verify(t *testing.T) {
tstore.Set("1", "1")
tstore.Set("2", "2")
type fields struct {
liveTime time.Duration
m *sync.Map
}
type args struct {
id string
answer string
clear bool
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{"get", fields{liveTime, tstore.m}, args{"1", "1", true}, true},
{"get", fields{liveTime, tstore.m}, args{"1", "1", false}, false},
{"get", fields{liveTime, tstore.m}, args{"2", "2", true}, true},
{"get", fields{liveTime, tstore.m}, args{"2", "2", false}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StoreSyncMap{
liveTime: tt.fields.liveTime,
m: tt.fields.m,
}
if got := s.Verify(tt.args.id, tt.args.answer, tt.args.clear); got != tt.want {
t.Errorf("Verify() = %v, want %v", got, tt.want)
}
})
}
}
func TestStoreSyncMap_rmExpire(t *testing.T) {
type fields struct {
liveTime time.Duration
m *sync.Map
}
tests := []struct {
name string
fields fields
}{
{"get", fields{liveTime, new(sync.Map)}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StoreSyncMap{
liveTime: tt.fields.liveTime,
m: tt.fields.m,
}
s.rmExpire()
})
}
}

@ -0,0 +1,94 @@
package base64Captcha
import (
"crypto/rand"
"fmt"
"io"
"os"
"path/filepath"
)
//parseDigitsToString parse randomDigits to normal string
func parseDigitsToString(bytes []byte) string {
stringB := make([]byte, len(bytes))
for idx, by := range bytes {
stringB[idx] = by + '0'
}
return string(stringB)
}
func stringToFakeByte(content string) []byte {
digits := make([]byte, len(content))
for idx, cc := range content {
digits[idx] = byte(cc - '0')
}
return digits
}
// randomDigits returns a byte slice of the given length containing
// pseudorandom numbers in range 0-9. The slice can be used as a captcha
// solution.
func randomDigits(length int) []byte {
return randomBytesMod(length, 10)
}
// randomBytes returns a byte slice of the given length read from CSPRNG.
func randomBytes(length int) (b []byte) {
b = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
panic("captcha: error reading random source: " + err.Error())
}
return
}
// randomBytesMod returns a byte slice of the given length, where each byte is
// a random number modulo mod.
func randomBytesMod(length int, mod byte) (b []byte) {
if length == 0 {
return nil
}
if mod == 0 {
panic("captcha: bad mod argument for randomBytesMod")
}
maxrb := 255 - byte(256%int(mod))
b = make([]byte, length)
i := 0
for {
r := randomBytes(length + (length / 4))
for _, c := range r {
if c > maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = c % mod
i++
if i == length {
return
}
}
}
}
func itemWriteFile(cap Item, outputDir, fileName, fileExt string) error {
filePath := filepath.Join(outputDir, fileName+"."+fileExt)
if !pathExists(outputDir) {
_ = os.MkdirAll(outputDir, os.ModePerm)
}
file, err := os.Create(filePath)
if err != nil {
fmt.Printf("%s is invalid path.error:%v", filePath, err)
return err
}
defer file.Close()
_, err = cap.WriteTo(file)
return err
}
func pathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}

@ -0,0 +1,77 @@
package base64Captcha
import (
"math"
)
const sampleRate = 8000 // Hz
var endingBeepSound []byte
func init() {
endingBeepSound = changeSpeed(beepSound, 1.4)
}
// mixSound mixes src into dst. Dst must have length equal to or greater than
// src length.
func mixSound(dst, src []byte) {
for i, v := range src {
av := int(v)
bv := int(dst[i])
if av < 128 && bv < 128 {
dst[i] = byte(av * bv / 128)
} else {
dst[i] = byte(2*(av+bv) - av*bv/128 - 256)
}
}
}
func setSoundLevel(a []byte, level float64) {
for i, v := range a {
av := float64(v)
switch {
case av > 128:
if av = (av-128)*level + 128; av < 128 {
av = 128
}
case av < 128:
if av = 128 - (128-av)*level; av > 128 {
av = 128
}
default:
continue
}
a[i] = byte(av)
}
}
// changeSpeed returns new PCM bytes from the bytes with the speed and pitch
// changed to the given value that must be in range [0, x].
func changeSpeed(a []byte, speed float64) []byte {
b := make([]byte, int(math.Floor(float64(len(a))*speed)))
var p float64
for _, v := range a {
for i := int(p); i < int(p+speed); i++ {
b[i] = v
}
p += speed
}
return b
}
func makeSilence(length int) []byte {
b := make([]byte, length)
for i := range b {
b[i] = 128
}
return b
}
func reversedSound(a []byte) []byte {
n := len(a)
b := make([]byte, n)
for i, v := range a {
b[n-1-i] = v
}
return b
}

@ -0,0 +1,103 @@
package base64Captcha
import (
"reflect"
"testing"
)
func Test_mixSound(t *testing.T) {
type args struct {
dst []byte
src []byte
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mixSound(tt.args.dst, tt.args.src)
})
}
}
func Test_setSoundLevel(t *testing.T) {
type args struct {
a []byte
level float64
}
tests := []struct {
name string
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setSoundLevel(tt.args.a, tt.args.level)
})
}
}
func Test_changeSpeed(t *testing.T) {
type args struct {
a []byte
speed float64
}
tests := []struct {
name string
args args
want []byte
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := changeSpeed(tt.args.a, tt.args.speed); !reflect.DeepEqual(got, tt.want) {
t.Errorf("changeSpeed() = %v, want %v", got, tt.want)
}
})
}
}
func Test_makeSilence(t *testing.T) {
type args struct {
length int
}
tests := []struct {
name string
args args
want []byte
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := makeSilence(tt.args.length); !reflect.DeepEqual(got, tt.want) {
t.Errorf("makeSilence() = %v, want %v", got, tt.want)
}
})
}
}
func Test_reversedSound(t *testing.T) {
type args struct {
a []byte
}
tests := []struct {
name string
args args
want []byte
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := reversedSound(tt.args.a); !reflect.DeepEqual(got, tt.want) {
t.Errorf("reversedSound() = %v, want %v", got, tt.want)
}
})
}
}

@ -0,0 +1,80 @@
package base64Captcha
import (
"os"
"path/filepath"
"reflect"
"testing"
)
func Test_parseDigitsToString(t *testing.T) {
for i := 1; i < 10; i++ {
digti := randomDigits(i)
s := parseDigitsToString(digti)
if len(s) != i {
t.Error("failed")
}
}
}
func Test_stringToFakeByte(t *testing.T) {
for i := 1; i < 10; i++ {
digti := randomDigits(i)
s := parseDigitsToString(digti)
if len(s) != i {
t.Error("failed")
}
fb := stringToFakeByte(s)
if !reflect.DeepEqual(fb, digti) {
t.Error("failed")
}
}
}
func Test_randomDigits(t *testing.T) {
for i := 1; i < 10; i++ {
digti := randomDigits(i)
if len(digti) != i {
t.Error("failed")
}
}
}
func Test_randomBytes(t *testing.T) {
for i := 1; i < 10; i++ {
digti := randomBytes(i)
if len(digti) != i {
t.Error("failed")
}
}
}
func Test_randomBytesMod(t *testing.T) {
for i := 1; i < 10; i++ {
digti := randomBytesMod(i, 'c')
if len(digti) != i {
t.Error("failed")
}
}
}
func Test_itemWriteFile(t *testing.T) {
//todo:::
}
func Test_pathExists(t *testing.T) {
td := os.TempDir()
defer os.RemoveAll(td)
p := filepath.Join(td, RandomId())
if pathExists(p) {
t.Error("failed")
}
_ = os.MkdirAll(p, os.ModePerm)
if !pathExists(p) {
t.Error("failed")
}
}

@ -1,32 +1,61 @@
package captcha
import (
"github.com/mojocn/base64Captcha"
"fmt"
"git.diulo.com/mogfee/protoc-gen-kit/pkg/base64Captcha"
"github.com/go-redis/redis"
"time"
)
func GetCaptcha() (string, string) {
//config struct for Character
//字符,公式,验证码配置
var configC = base64Captcha.ConfigCharacter{
Height: 30,
Width: 80,
//const CaptchaModeNumber:数字,CaptchaModeAlphabet:字母,CaptchaModeArithmetic:算术,CaptchaModeNumberAlphabet:数字字母混合.
Mode: base64Captcha.CaptchaModeNumber,
ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
ComplexOfNoiseDot: base64Captcha.CaptchaComplexLower,
IsUseSimpleFont: true,
IsShowHollowLine: false,
IsShowNoiseDot: false,
IsShowNoiseText: false,
IsShowSlimeLine: false,
IsShowSineLine: false,
CaptchaLen: 4,
var DefaultConfig = base64Captcha.DriverString{
Height: 80,
Width: 240,
NoiseCount: 20,
ShowLineOptions: 100,
Length: 5,
BgColor: nil,
Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
Fonts: []string{"wqy-microhei.ttc", "chromohv.ttf", "actionj.ttf", "RitaSmith.ttf"},
}
func Generate(store base64Captcha.Store) (id, b64s string, err error) {
return base64Captcha.NewCaptcha(&DefaultConfig, store).Generate()
}
type redisStore struct {
redis *redis.Client
server string
}
func NewRedisStore(server string, redis *redis.Client) *redisStore {
return &redisStore{
redis: redis,
server: server,
}
}
func (r *redisStore) Key(key string) string {
return fmt.Sprintf("captach:%s:%s", r.server, key)
}
func (r *redisStore) Set(id string, value string) error {
return r.redis.Set(r.Key(id), value, time.Second*3700).Err()
}
func (r *redisStore) Get(id string, clear bool) string {
val, err := r.redis.Get(r.Key(id)).Result()
if err != nil {
return ""
}
if clear {
r.redis.Del(r.Key(id))
}
idKeyC, capC := base64Captcha.GenerateCaptcha("", configC)
base64stringC := base64Captcha.CaptchaWriteToBase64Encoding(capC)
return idKeyC, base64stringC
return val
}
func Verify(idKey, verifyValue string) bool {
return base64Captcha.VerifyCaptcha(idKey, verifyValue)
func (r *redisStore) Verify(id, answer string, clear bool) bool {
val := r.Get(id, clear)
if answer == "" {
return false
}
return val == answer
}

@ -0,0 +1,15 @@
package captcha
import (
"fmt"
"github.com/go-redis/redis"
"testing"
)
func TestGenerateKey(t *testing.T) {
client := redis.NewClient(&redis.Options{})
store := NewRedisStore("user", client)
id, _, err := Generate(store)
fmt.Println(id, err)
fmt.Println(store.Verify(id, "aa", false))
}
Loading…
Cancel
Save