diff --git a/app/Http/Controllers/Api/V1/CaptchaController.php b/app/Http/Controllers/Api/V1/CaptchaController.php new file mode 100644 index 00000000..7188be44 --- /dev/null +++ b/app/Http/Controllers/Api/V1/CaptchaController.php @@ -0,0 +1,73 @@ +filled('key') ? $request->input('key') : Str::random(16); + + $builder = $this->builder(); + $builder->build( + (int) $request->input('w', 150), + (int) $request->input('h', 40), + ); + + if (strlen($key) <= 32) { + $captchaService->put($key, $builder->getPhrase()); + } + + return response()->json([ + 'key' => $key, + 'img' => $builder->inline(), + ]); + } + + /** + * 查看图形验证码 + * + * @param string $key + * @param \Illuminate\Http\Request $request + * @param \App\Services\CaptchaService $captchaService + * @return \Illuminate\Http\Response + */ + public function show($key, Request $request, CaptchaService $captchaService) + { + $builder = $this->builder(); + $builder->build( + (int) $request->query('w', 150), + (int) $request->query('h', 40), + ); + + if (strlen($key) <= 32) { + $captchaService->put($key, $builder->getPhrase()); + } + + return response($builder->get()) + ->header('Content-Type', 'image/jpeg') + ->header('Cache-Control', 'no-cache'); + } + + /** + * 图形验证码生成器 + * + * @return \Gregwar\Captcha\CaptchaBuilder + */ + protected function builder(): CaptchaBuilder + { + return new CaptchaBuilder(Str::random(5)); + } +} diff --git a/app/Services/CaptchaService.php b/app/Services/CaptchaService.php new file mode 100644 index 00000000..fa58a02b --- /dev/null +++ b/app/Services/CaptchaService.php @@ -0,0 +1,82 @@ +cache->put($this->cacheKey($key), $phrase, $ttl); + } + + /** + * 校验验证码是否正确 + * + * @param string $key + * @param string $phrase + * @return bool + */ + public function testPhrase(string $key, string $phrase): bool + { + if ($phrase === '') { + return false; + } + + $value = (string) $this->cache->pull( + $this->cacheKey($key) + ); + + return strtolower($value) === strtolower($phrase); + } + + /** + * 校验验证码是否正确 + * + * @param string $key + * @param string $phrase + * @return void + * + * @throws \App\Exceptions\BizException + */ + public function validatePhrase(string $key, string $phrase): void + { + if (! $this->testPhrase($key, $phrase)) { + throw new BizException(__('Invalid captcha')); + } + } + + /** + * 生成验证码缓存的 key + * + * @param string $key + * @return string + */ + protected function cacheKey(string $key): string + { + return $this->cachePrefix.$key; + } +} diff --git a/composer.json b/composer.json index cd7bca12..4e32b0c1 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "php": "^8.0", "dcat/laravel-admin": "2.1.5-beta", "fruitcake/laravel-cors": "^2.0", + "gregwar/captcha": "^1.1", "guzzlehttp/guzzle": "^7.0.1", "kalnoy/nestedset": "^6.0", "laravel/framework": "^8.65", diff --git a/composer.lock b/composer.lock index 00ca4326..c1d8b148 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "50de900b3113c2d3d3f9c191fd6e6693", + "content-hash": "865b93f26360dff74bb3e2c2c6dac800", "packages": [ { "name": "asm89/stack-cors", @@ -1133,6 +1133,69 @@ ], "time": "2021-10-17T19:48:54+00:00" }, + { + "name": "gregwar/captcha", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "captcha", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "issues": "https://github.com/Gregwar/Captcha/issues", + "source": "https://github.com/Gregwar/Captcha/tree/master" + }, + "time": "2020-03-24T14:39:05+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.4.0", diff --git a/resources/lang/zh_CN.json b/resources/lang/zh_CN.json index 5e512a10..768f84be 100644 --- a/resources/lang/zh_CN.json +++ b/resources/lang/zh_CN.json @@ -1,5 +1,6 @@ { ":resource not found": ":resource 未找到", + "Invalid captcha": "无效的验证码", "Invalid invitation code": "无效的邀请码", "Registration failed, please try again": "注册失败,请重试" } diff --git a/routes/api/v1/common.php b/routes/api/v1/common.php new file mode 100644 index 00000000..b260c50c --- /dev/null +++ b/routes/api/v1/common.php @@ -0,0 +1,7 @@ +