@ -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(¶m) |
||||
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(¶m) |
||||
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) |
||||
} |
||||
} |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 4.0 KiB |
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> |
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, |
||||
}, |
||||
} |
@ -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) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -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)) |
||||
} |