├── .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 |
captcha
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 "captcha"; 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 | --------------------------------------------------------------------------------