chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ywxapp\service;
|
||||
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 图形验证码服务(API 友好,基于 GD + Cache,无需 session)
|
||||
*
|
||||
* 设计要点:
|
||||
* - 生成时返回 {token, image(base64), expire},前端把 image 直接塞进 <img src>,
|
||||
* 提交时回传 token + 用户输入的 captcha;服务端比对 Cache 中的 token 值。
|
||||
* - 与 think-captcha 的 session 方案不同,本服务完全无状态,适配小程序 / APP / SPA 等场景。
|
||||
* - 验证码一次性(校验成功后立即失效),有效期 120s。
|
||||
*
|
||||
* @package ywxapp\service
|
||||
*/
|
||||
class CaptchaService
|
||||
{
|
||||
const PREFIX = 'captcha_';
|
||||
const EXPIRE = 120;
|
||||
|
||||
|
||||
public static function instance(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图形验证码
|
||||
*
|
||||
* @param int $length 字符数
|
||||
* @return array ['token'=>string, 'image'=>string(base64 data uri), 'expire'=>int]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function generate(int $length = 4): array
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new \Exception('服务器未启用 GD 扩展,无法生成图形验证码');
|
||||
}
|
||||
$code = $this->randomCode($length);
|
||||
$token = bin2hex(random_bytes(16));
|
||||
Cache::set(self::PREFIX . $token, strtolower($code), self::EXPIRE);
|
||||
return [
|
||||
'token' => $token,
|
||||
'image' => $this->renderImage($code),
|
||||
'expire' => self::EXPIRE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验图形验证码(一次性)
|
||||
*
|
||||
* @param string $token 生成时返回的 token
|
||||
* @param string $code 用户输入
|
||||
* @return bool
|
||||
*/
|
||||
public function verify(string $token, string $code): bool
|
||||
{
|
||||
if ($token === '' || $code === '') {
|
||||
return false;
|
||||
}
|
||||
$key = self::PREFIX . $token;
|
||||
$stored = Cache::get($key);
|
||||
if ($stored === null) {
|
||||
return false;
|
||||
}
|
||||
$ok = strtolower(trim($code)) === strtolower((string) $stored);
|
||||
if ($ok) {
|
||||
Cache::delete($key);
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机可读字符(去除易混淆的 0/O/1/I/L)
|
||||
*/
|
||||
protected function randomCode(int $length): string
|
||||
{
|
||||
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
$max = strlen($chars) - 1;
|
||||
$code = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$code .= $chars[mt_rand(0, $max)];
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 GD 绘制验证码 PNG,返回 data URI
|
||||
*/
|
||||
protected function renderImage(string $code): string
|
||||
{
|
||||
$w = 120;
|
||||
$h = 44;
|
||||
$img = imagecreatetruecolor($w, $h);
|
||||
// 背景
|
||||
$bg = imagecolorallocate($img, 240, 242, 245);
|
||||
imagefill($img, 0, 0, $bg);
|
||||
|
||||
// 干扰点
|
||||
for ($i = 0; $i < 80; $i++) {
|
||||
$c = imagecolorallocate($img, mt_rand(170, 230), mt_rand(170, 230), mt_rand(170, 230));
|
||||
imagesetpixel($img, mt_rand(0, $w - 1), mt_rand(0, $h - 1), $c);
|
||||
}
|
||||
// 干扰线
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$c = imagecolorallocate($img, mt_rand(150, 210), mt_rand(150, 210), mt_rand(150, 210));
|
||||
imageline($img, mt_rand(0, $w), mt_rand(0, $h), mt_rand(0, $w), mt_rand(0, $h), $c);
|
||||
}
|
||||
|
||||
// 字符
|
||||
$len = strlen($code);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$color = imagecolorallocate($img, mt_rand(30, 130), mt_rand(30, 130), mt_rand(30, 130));
|
||||
$size = mt_rand(5, 5); // imagechar 内置字体大小(1-5)
|
||||
$x = (int) (10 + $i * ($w / $len));
|
||||
$y = mt_rand(8, 16);
|
||||
imagechar($img, $size, $x, $y, $code[$i], $color);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
imagepng($img);
|
||||
$raw = ob_get_clean();
|
||||
imagedestroy($img);
|
||||
|
||||
return 'data:image/png;base64,' . base64_encode($raw);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user