├── .gitignore
├── assets
└── ttfs
│ ├── 1.ttf
│ ├── 2.ttf
│ ├── 4.ttf
│ └── 5.ttf
├── src
├── facade
│ └── Captcha.php
├── CaptchaService.php
├── CaptchaController.php
├── config.php
├── helper.php
└── Captcha.php
├── composer.json
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /composer.lock
3 | .idea
--------------------------------------------------------------------------------
/assets/ttfs/1.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netcccyun/think-captcha/3.0/assets/ttfs/1.ttf
--------------------------------------------------------------------------------
/assets/ttfs/2.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netcccyun/think-captcha/3.0/assets/ttfs/2.ttf
--------------------------------------------------------------------------------
/assets/ttfs/4.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netcccyun/think-captcha/3.0/assets/ttfs/4.ttf
--------------------------------------------------------------------------------
/assets/ttfs/5.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netcccyun/think-captcha/3.0/assets/ttfs/5.ttf
--------------------------------------------------------------------------------
/src/facade/Captcha.php:
--------------------------------------------------------------------------------
1 | extend('captcha', function ($value) {
15 | return captcha_check($value);
16 | }, ':attribute错误!');
17 | });
18 |
19 | $this->registerRoutes(function (Route $route) {
20 | $route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index");
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "topthink/think-captcha",
3 | "description": "captcha package for thinkphp",
4 | "authors": [
5 | {
6 | "name": "yunwuxin",
7 | "email": "448901948@qq.com"
8 | }
9 | ],
10 | "license": "Apache-2.0",
11 | "require": {
12 | "topthink/framework": "^6.0|^8.0"
13 | },
14 | "autoload": {
15 | "psr-4": {
16 | "think\\captcha\\": "src/"
17 | },
18 | "files": [
19 | "src/helper.php"
20 | ]
21 | },
22 | "extra": {
23 | "think": {
24 | "services": [
25 | "think\\captcha\\CaptchaService"
26 | ],
27 | "config":{
28 | "captcha": "src/config.php"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/CaptchaController.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\captcha;
13 |
14 | class CaptchaController
15 | {
16 | public function index(Captcha $captcha, $config = null)
17 | {
18 | return $captcha->create($config);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # think-captcha
2 |
3 | thinkphp 验证码类库(精简版)
4 |
5 | ## 安装
6 |
7 | > composer require cccyun/think-captcha
8 |
9 | ## 使用
10 |
11 | ### 在控制器中输出验证码
12 |
13 | 在控制器的操作方法中使用
14 |
15 | ```
16 | public function captcha($id = '')
17 | {
18 | return captcha($id);
19 | }
20 | ```
21 |
22 | 然后注册对应的路由来输出验证码
23 |
24 | ### 模板里输出验证码
25 |
26 | 首先要在你应用的路由定义文件中,注册一个验证码路由规则。
27 |
28 | ```
29 | \think\facade\Route::get('captcha/[:id]', "\\think\\captcha\\CaptchaController@index");
30 | ```
31 |
32 | 然后就可以在模板文件中使用
33 |
34 | ```
35 |
{:captcha_img()}
36 | ```
37 |
38 | 或者
39 |
40 | ```
41 |
42 | ```
43 |
44 | > 上面两种的最终效果是一样的
45 |
46 | ### 控制器里验证
47 |
48 | 使用 TP 的内置验证功能即可
49 |
50 | ```
51 | $this->validate($data,[
52 | 'captcha|验证码'=>'require|captcha'
53 | ]);
54 | ```
55 |
56 | 或者手动验证
57 |
58 | ```
59 | if(!captcha_check($captcha)){
60 | //验证失败
61 | };
62 | ```
63 |
--------------------------------------------------------------------------------
/src/config.php:
--------------------------------------------------------------------------------
1 | 5,
9 | // 验证码字符集合
10 | 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
11 | // 验证码过期时间
12 | 'expire' => 1800,
13 | // 是否使用中文验证码
14 | 'useZh' => false,
15 | // 是否使用算术验证码
16 | 'math' => false,
17 | // 是否使用背景图
18 | 'useImgBg' => false,
19 | //验证码字符大小
20 | 'fontSize' => 25,
21 | // 是否使用混淆曲线
22 | 'useCurve' => true,
23 | //是否添加杂点
24 | 'useNoise' => true,
25 | // 验证码字体 不设置则随机
26 | 'fontttf' => '',
27 | //背景颜色
28 | 'bg' => [243, 251, 254],
29 | // 验证码图片高度
30 | 'imageH' => 0,
31 | // 验证码图片宽度
32 | 'imageW' => 0,
33 | // 验证码图片透明度
34 | 'alpha' => 0,
35 | // 是否采用API模式生成
36 | 'api' => false,
37 |
38 | // 添加额外的验证码设置
39 | // verify => [
40 | // 'length'=>4,
41 | // ...
42 | //],
43 | ];
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
3 | 版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
4 | All rights reserved。
5 | ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
6 |
7 | Apache Licence是著名的非盈利开源组织Apache采用的协议。
8 | 该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
9 | 允许代码修改,再作为开源或商业软件发布。需要满足
10 | 的条件:
11 | 1. 需要给代码的用户一份Apache Licence ;
12 | 2. 如果你修改了代码,需要在被修改的文件中说明;
13 | 3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
14 | 带有原来代码中的协议,商标,专利声明和其他原来作者规
15 | 定需要包含的说明;
16 | 4. 如果再发布的产品中包含一个Notice文件,则在Notice文
17 | 件中需要带有本协议内容。你可以在Notice中增加自己的
18 | 许可,但不可以表现为对Apache Licence构成更改。
19 | 具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 | POSSIBILITY OF SUCH DAMAGE.
33 |
--------------------------------------------------------------------------------
/src/helper.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | use think\captcha\facade\Captcha;
13 | use think\facade\Route;
14 | use think\Response;
15 |
16 | /**
17 | * @param string $config
18 | * @return \think\Response
19 | */
20 | function captcha($config = null): Response
21 | {
22 | return Captcha::create($config);
23 | }
24 |
25 | /**
26 | * @param $config
27 | * @return string
28 | */
29 | function captcha_src($config = null): string
30 | {
31 | return Route::buildUrl('/captcha' . ($config ? "/{$config}" : ''));
32 | }
33 |
34 | /**
35 | * @param $id
36 | * @return string
37 | */
38 | function captcha_img($id = '', $domid = ''): string
39 | {
40 | $src = captcha_src($id);
41 |
42 | $domid = empty($domid) ? $domid : "id='" . $domid . "'";
43 |
44 | return "
";
45 | }
46 |
47 | /**
48 | * @param string $value
49 | * @return bool
50 | */
51 | function captcha_check($value)
52 | {
53 | return Captcha::check($value);
54 | }
55 |
--------------------------------------------------------------------------------
/src/Captcha.php:
--------------------------------------------------------------------------------
1 |
10 | // +----------------------------------------------------------------------
11 |
12 | namespace think\captcha;
13 |
14 | use Closure;
15 | use Exception;
16 | use think\Config;
17 | use think\Response;
18 | use think\Session;
19 |
20 | class Captcha
21 | {
22 | private $im = null; // 验证码图片实例
23 | private $color = null; // 验证码字体颜色
24 |
25 | /**
26 | * @var Config|null
27 | */
28 | private $config = null;
29 |
30 | /**
31 | * @var Session|null
32 | */
33 | private $session = null;
34 |
35 | // 验证码字符集合
36 | protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
37 | // 验证码过期时间(s)
38 | protected $expire = 1800;
39 | // 使用中文验证码
40 | protected $useZh = false;
41 | // 中文验证码字符串
42 | protected $zhSet = '';
43 | // 使用背景图片
44 | protected $useImgBg = false;
45 | // 是否画混淆曲线
46 | protected $useCurve = true;
47 | // 是否添加杂点
48 | protected $useNoise = true;
49 | // 验证码图片高度
50 | protected $imageH = 0;
51 | // 验证码图片宽度
52 | protected $imageW = 0;
53 | // 验证码位数
54 | protected $length = 5;
55 | // 验证码字体大小(px)
56 | protected $fontSize = 25;
57 | // 验证码字体,不设置随机获取
58 | protected $fontttf = '';
59 | // 背景颜色
60 | protected $bg = [243, 251, 254];
61 | //算术验证码
62 | protected $math = false;
63 | //从 0 到 127。0 表示完全不透明,127 表示完全透明。
64 | protected $alpha = 0;
65 | // 使用API模式生成
66 | protected $api = false;
67 | // 验证码干扰项及处理方法
68 | protected $interfere = [
69 | 'useImgBg' => 'background',
70 | 'useCurve' => 'writeCurve',
71 | 'useNoise' => 'writeNoise',
72 | ];
73 |
74 | /**
75 | * 架构方法 设置参数
76 | * @access public
77 | * @param Config $config
78 | * @param Session $session
79 | */
80 | public function __construct(Config $config, Session $session)
81 | {
82 | $this->config = $config;
83 | $this->session = $session;
84 | }
85 |
86 | /**
87 | * 配置验证码
88 | * @param string|null $config
89 | */
90 | protected function configure(?string $config = null): void
91 | {
92 | if (is_null($config)) {
93 | $config = $this->config->get('captcha', []);
94 | } else {
95 | $config = $this->config->get('captcha.' . $config, []);
96 | }
97 |
98 | foreach ($config as $key => $val) {
99 | if (property_exists($this, $key)) {
100 | $this->{$key} = $val;
101 | }
102 | }
103 | }
104 |
105 | /**
106 | * 创建验证码
107 | * @return array
108 | * @throws Exception
109 | */
110 | protected function generate(): array
111 | {
112 | $bag = '';
113 |
114 | if ($this->math) {
115 | $this->useZh = false;
116 | $this->length = 5;
117 |
118 | $x = random_int(10, 30);
119 | $y = random_int(1, 9);
120 | $bag = "{$x} + {$y} = ";
121 | $key = $x + $y;
122 | $key .= '';
123 | } else {
124 | if ($this->useZh) {
125 | $characters = preg_split('/(?zhSet);
126 | } else {
127 | $characters = str_split($this->codeSet);
128 | }
129 |
130 | for ($i = 0; $i < $this->length; $i++) {
131 | $bag .= $characters[random_int(0, count($characters) - 1)];
132 | }
133 |
134 | $key = mb_strtolower($bag, 'UTF-8');
135 | }
136 |
137 | $hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]);
138 |
139 | $this->session->set('captcha', [
140 | 'key' => $hash,
141 | ]);
142 |
143 | return [
144 | 'value' => $bag,
145 | 'key' => $hash,
146 | ];
147 | }
148 |
149 | /**
150 | * 验证验证码是否正确
151 | * @access public
152 | * @param string $code 用户验证码
153 | * @return bool 用户验证码是否正确
154 | */
155 | public function check(string $code): bool
156 | {
157 | if (!$this->session->has('captcha')) {
158 | return false;
159 | }
160 |
161 | $key = $this->session->get('captcha.key');
162 | $code = mb_strtolower($code, 'UTF-8');
163 | $res = password_verify($code, $key);
164 |
165 | if ($res) {
166 | $this->session->delete('captcha');
167 | }
168 |
169 | return $res;
170 | }
171 |
172 | /**
173 | * 输出验证码并把验证码的值保存的session中
174 | * @access public
175 | * @param null|string $config
176 | * @return Response|array
177 | */
178 | public function create(?string $config = null)
179 | {
180 | $this->configure($config);
181 |
182 | $generator = $this->generate();
183 |
184 | $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;
185 | $this->imageH || $this->imageH = $this->fontSize * 2.5;
186 |
187 | $this->imageW = (int) $this->imageW;
188 | $this->imageH = (int) $this->imageH;
189 |
190 | // 建立一幅 $this->imageW x $this->imageH 的图像
191 | $this->im = imagecreate($this->imageW, $this->imageH);
192 |
193 | // 设置背景
194 | imagecolorallocatealpha($this->im, $this->bg[0], $this->bg[1], $this->bg[2], $this->alpha);
195 |
196 | // 验证码字体随机颜色
197 | $this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
198 |
199 | // 验证码使用随机字体
200 | $ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/';
201 |
202 | if (empty($this->fontttf)) {
203 | $dir = dir($ttfPath);
204 | $ttfs = [];
205 | while (false !== ($file = $dir->read())) {
206 | if (substr($file, -4) === '.ttf' || substr($file, -4) === '.otf') {
207 | $ttfs[] = $file;
208 | }
209 | }
210 | $dir->close();
211 | $this->fontttf = $ttfs[array_rand($ttfs)];
212 | }
213 |
214 | $fontttf = $ttfPath . $this->fontttf;
215 |
216 | // 添加干扰项
217 | foreach ($this->interfere as $type => $method) {
218 | if ($method instanceof Closure) {
219 | $method($this->im, $this->imageW, $this->imageH, $this->fontSize, $this->color);
220 | } elseif ($this->$type) {
221 | $this->$method();
222 | }
223 | }
224 |
225 | // 绘验证码
226 | $text = $this->useZh ? preg_split('/(? $char) {
229 | $x = $this->fontSize * ($index + 1) * ($this->math ? 1 : 1.5);
230 | $y = $this->fontSize + mt_rand(10, 20);
231 | $angle = $this->math ? 0 : mt_rand(-40, 40);
232 |
233 | imagettftext($this->im, (int) $this->fontSize, $angle, (int) $x, (int) $y, $this->color, $fontttf, $char);
234 | }
235 |
236 | ob_start();
237 | // 输出图像
238 | imagepng($this->im);
239 | $content = ob_get_clean();
240 | imagedestroy($this->im);
241 |
242 | // API调用模式
243 | if ($this->api) {
244 | return [
245 | 'code' => implode('', $text),
246 | 'img' => 'data:image/png;base64,' . base64_encode($content),
247 | ];
248 | }
249 | // 输出验证码图片
250 | return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png');
251 | }
252 |
253 | /**
254 | * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数)
255 | *
256 | * 高中的数学公式咋都忘了涅,写出来
257 | * 正弦型函数解析式:y=Asin(ωx+φ)+b
258 | * 各常数值对函数图像的影响:
259 | * A:决定峰值(即纵向拉伸压缩的倍数)
260 | * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
261 | * φ:决定波形与X轴位置关系或横向移动距离(左加右减)
262 | * ω:决定周期(最小正周期T=2π/∣ω∣)
263 | *
264 | */
265 | protected function writeCurve(): void
266 | {
267 | $px = $py = 0;
268 |
269 | // 曲线前部分
270 | $A = mt_rand(1, (int) ($this->imageH / 2)); // 振幅
271 | $b = mt_rand((int) (-$this->imageH / 4), (int) ($this->imageH / 4)); // Y轴方向偏移量
272 | $f = mt_rand((int) (-$this->imageH / 4), (int) ($this->imageH / 4)); // X轴方向偏移量
273 | $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
274 | $w = (2 * M_PI) / $T;
275 |
276 | $px1 = 0; // 曲线横坐标起始位置
277 | $px2 = mt_rand((int) ($this->imageW / 2), (int) ($this->imageW * 0.8)); // 曲线横坐标结束位置
278 |
279 | for ($px = $px1; $px <= $px2; $px = $px + 1) {
280 | if (0 != $w) {
281 | $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
282 | $i = (int) ($this->fontSize / 5);
283 | while ($i > 0) {
284 | imagesetpixel($this->im, (int) ($px + $i), (int) ($py + $i), $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多
285 | $i--;
286 | }
287 | }
288 | }
289 |
290 | // 曲线后部分
291 | $A = mt_rand(1, (int) ($this->imageH / 2)); // 振幅
292 | $f = mt_rand((int) (-$this->imageH / 4), (int) ($this->imageH / 4)); // X轴方向偏移量
293 | $T = mt_rand($this->imageH, $this->imageW * 2); // 周期
294 | $w = (2 * M_PI) / $T;
295 | $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
296 | $px1 = $px2;
297 | $px2 = $this->imageW;
298 |
299 | for ($px = $px1; $px <= $px2; $px = $px + 1) {
300 | if (0 != $w) {
301 | $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
302 | $i = (int) ($this->fontSize / 5);
303 | while ($i > 0) {
304 | imagesetpixel($this->im, (int) ($px + $i), (int) ($py + $i), $this->color);
305 | $i--;
306 | }
307 | }
308 | }
309 | }
310 |
311 | /**
312 | * 画杂点
313 | * 往图片上写不同颜色的字母或数字
314 | */
315 | protected function writeNoise(): void
316 | {
317 | $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
318 | for ($i = 0; $i < 10; $i++) {
319 | //杂点颜色
320 | $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
321 | for ($j = 0; $j < 5; $j++) {
322 | // 绘杂点
323 | imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor);
324 | }
325 | }
326 | }
327 |
328 | /**
329 | * 绘制背景图片
330 | * 注:如果验证码输出图片比较大,将占用比较多的系统资源
331 | */
332 | protected function background(): void
333 | {
334 | $path = __DIR__ . '/../assets/bgs/';
335 | $dir = dir($path);
336 |
337 | $bgs = [];
338 | while (false !== ($file = $dir->read())) {
339 | if ('.' != $file[0] && substr($file, -4) == '.jpg') {
340 | $bgs[] = $path . $file;
341 | }
342 | }
343 | $dir->close();
344 |
345 | $gb = $bgs[array_rand($bgs)];
346 |
347 | [$width, $height] = @getimagesize($gb);
348 | // Resample
349 | $bgImage = @imagecreatefromjpeg($gb);
350 | @imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height);
351 | @imagedestroy($bgImage);
352 | }
353 | }
354 |
--------------------------------------------------------------------------------