Files
jyshd_mWtK2/application/api/controller/Common.php
T
li 733982a2be feat: 注册验证码改为自动生成图形验证码
1. Common.php: 新增captcha()接口,生成算术验证码图片(base64)
2. Common.php: 新增checkCaptcha()静态方法,验证码校验+防重放
3. User.php: 注册接口移除邮箱/短信OTP验证,改用图形验证码
验证码5分钟过期,验证后立即销毁
2026-03-31 10:53:33 +08:00

431 lines
15 KiB
PHP
Executable File

<?php
namespace app\api\controller;
use app\common\controller\Api;
use app\common\exception\UploadException;
use app\common\library\Upload;
use app\common\model\Area;
use app\common\model\Version;
use fast\Random;
use think\Config;
use think\Hook;
use think\Db;
/**
* 公共接口
*/
class Common extends Api
{
protected $noNeedLogin = "*";
protected $noNeedRight = '*';
/**
* 加载初始化
*
* @param string $version 版本号
* @param string $lng 经度
* @param string $lat 纬度
*/
public function init()
{
if ($version = $this->request->request('version')) {
$lng = $this->request->request('lng');
$lat = $this->request->request('lat');
//配置信息
$upload = Config::get('upload');
//如果非服务端中转模式需要修改为中转
if ($upload['storage'] != 'local' && isset($upload['uploadmode']) && $upload['uploadmode'] != 'server') {
//临时修改上传模式为服务端中转
set_addon_config($upload['storage'], ["uploadmode" => "server"], false);
$upload = \app\common\model\Config::upload();
// 上传信息配置后
Hook::listen("upload_config_init", $upload);
$upload = Config::set('upload', array_merge(Config::get('upload'), $upload));
}
$upload['cdnurl'] = $upload['cdnurl'] ? $upload['cdnurl'] : cdnurl('', true);
$upload['uploadurl'] = preg_match("/^((?:[a-z]+:)?\/\/)(.*)/i", $upload['uploadurl']) ? $upload['uploadurl'] : url($upload['storage'] == 'local' ? '/api/common/upload' : $upload['uploadurl'], '', false, true);
$content = [
'citydata' => Area::getCityFromLngLat($lng, $lat),
'versiondata' => Version::check($version),
'uploaddata' => $upload,
'coverdata' => Config::get("cover"),
];
$this->success('', $content);
} else {
$this->error(__('Invalid parameters'));
}
}
/**
* 上传文件
* @ApiMethod (POST)
* @param File $file 文件流
*/
public function upload()
{
Config::set('default_return_type', 'json');
//必须设定cdnurl为空,否则cdnurl函数计算错误
Config::set('upload.cdnurl', '');
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
}
} else {
$attachment = null;
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
}
}
/**
* 单文件上传公共
* @param [type] $file_url [保存地址]
* @return [type] [description]
*/
public function iamge_upload_single($file_url)
{
$file = $this->request->file('file');
if (empty($file)) {
$this->error("请选择文件");
}
$url = $file_url;
$dst_img = $this->upload_image($file, $url);
if ($dst_img) {
if(mb_substr($dst_img, 0,1) != "/"){
$dst_img = "/".$dst_img;
}
$this->success("Upload successful", [
'iamge_url' => Config::get('site.image_url').$dst_img,
'url' => $dst_img,
]);
} else {
$this->error("Upload failed", [
'info' => $this->error('error'),
]);
}
}
/**
* 公共上传图片
* $file 上传对象
* by sen
*/
public function upload_image($file, $category) {
//判断是否已经存在附件
$sha1 = $file->hash();
$upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int) $upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix ? $suffix : 'file';
$mimetypeArr = explode(',', strtolower($upload['mimetype']));
$typeArr = explode('/', $fileInfo['type']);
//验证文件后缀
if ($upload['mimetype'] !== '*' &&
(
!in_array($suffix, $mimetypeArr) || (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))
)
) {
$this->error(__('Uploaded file format is limited'));
}
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => md5_file($fileInfo['tmp_name']),
];
$savekey = $upload['savekey'];
// $savekey = $upload['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
$uploadDir = $category . substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo) {
$imagewidth = $imageheight = 0;
if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'])) {
$imgInfo = getimagesize($splInfo->getPathname());
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$params = array(
'admin_id' => 0,
'user_id' => (int) $this->auth->id,
'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth,
'imageheight' => $imageheight,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $fileInfo['type'],
'url' => $uploadDir . $splInfo->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
);
$attachment = model("attachment");
$attachment->data(array_filter($params));
$attachment->save();
\think\Hook::listen("upload_after", $attachment);
//-----压缩
import('lib.imgcompress', EXTEND_PATH , '.class.php');
$source = $uploadDir . $splInfo->getSaveName();
$dst_img = substr($uploadDir . 'compress_' . $fileName, 1); //可加存放路径
$percent = 1; #原图压缩,不缩放
$new_resource = substr($source, 1);
$imgcompress = new \imgcompress($new_resource, $percent);
$image = $imgcompress->compressImg($dst_img);
unset($splInfo);
@unlink($new_resource);
return $dst_img;
} else {
// 上传失败获取错误信息
return $this->error($file->getError());
}
}
/**
* 生成二维码
* $param 生成参数
* $filename 文件名
*/
public function qrcode_s($param, $file_name , $matrixPointSize=8) {
header("Content-type: text/html; charset=utf-8");
import('lib.qrcode', EXTEND_PATH , '.php');
$QRcode = new \QRcode();
$errorCorrectionLevel = 'L'; //容错级别
// $matrixPointSize = 8; //生成图片大小
//生成二维码图片
$filename = $file_name . '.png';
$res = $QRcode->png($param, $filename, $errorCorrectionLevel, $matrixPointSize, 2);
$info = '/' . $filename;
return $info;
}
/**
*
* @param type $type 1=初级认证,2=高级认证
* @return boolean
*/
public function get_auth($type = 1){
if($type == 1){
$auth = Db::name("app_auth")->where("user_id",$this->auth->id)->where("status",1)->find();
if(empty($auth)){
return false;
}else{
return true;
}
}else{
$auth = Db::name("app_auth")->where("user_id",$this->auth->id)->where("status",1)->find();
if(empty($auth)){
return false;
}else{
return true;
}
}
}
/**
* TRC加密
*/
public function trc_encryption($key)
{
$str = "123123456789456789qwertyuioplkjhgfdsazxcvbnm123456789";
$keys = substr(str_shuffle($str), 0,8).$key.substr(str_shuffle($str), 0,9);
$string = base64_encode($keys);
return $string;
}
/**
* TRC解密
*/
public function trc_decrypt($key)
{
$string = base64_decode($key);
$key = substr($string, 8,-9);
return $key;
}
/**
* 注册验证码图片 - 生成算术验证码并返回base64图片
* GET /api/common/captcha
*/
public function captcha()
{
$a = mt_rand(1, 20);
$b = mt_rand(1, 20);
$operators = ['+', '-'];
$op = $operators[array_rand($operators)];
if ($op == '-' && $a < $b) {
// 确保减法结果非负
$tmp = $a; $a = $b; $b = $tmp;
}
$answer = ($op == '+') ? ($a + $b) : ($a - $b);
$text = "{$a} {$op} {$b} = ?";
// 生成唯一key标识本次验证码
$captcha_key = md5(uniqid(mt_rand(), true));
// 将答案存入缓存(使用ThinkPHP缓存,5分钟过期)
cache('captcha_' . $captcha_key, strval($answer), 300);
// 生成验证码图片
$width = 200;
$height = 60;
$img = imagecreatetruecolor($width, $height);
// 背景色
$bgColor = imagecolorallocate($img, 240, 244, 250);
imagefilledrectangle($img, 0, 0, $width, $height, $bgColor);
// 干扰线
for ($i = 0; $i < 5; $i++) {
$lineColor = imagecolorallocate($img, mt_rand(100, 200), mt_rand(100, 200), mt_rand(100, 200));
imageline($img, mt_rand(0, $width), mt_rand(0, $height), mt_rand(0, $width), mt_rand(0, $height), $lineColor);
}
// 干扰点
for ($i = 0; $i < 50; $i++) {
$dotColor = imagecolorallocate($img, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
imagesetpixel($img, mt_rand(0, $width), mt_rand(0, $height), $dotColor);
}
// 文字颜色
$textColor = imagecolorallocate($img, mt_rand(10, 80), mt_rand(10, 80), mt_rand(10, 80));
// 使用内置字体绘制文字
$fontSize = 5; // imagestring内置字体最大5
$textWidth = imagefontwidth($fontSize) * strlen($text);
$textHeight = imagefontheight($fontSize);
$x = ($width - $textWidth) / 2;
$y = ($height - $textHeight) / 2;
imagestring($img, $fontSize, $x, $y, $text, $textColor);
// 输出为base64
ob_start();
imagepng($img);
$imgData = ob_get_clean();
imagedestroy($img);
$base64 = 'data:image/png;base64,' . base64_encode($imgData);
$this->success('ok', [
'captcha_key' => $captcha_key,
'captcha_img' => $base64,
]);
}
/**
* 验证注册验证码(内部调用)
*/
public static function checkCaptcha($captcha_key, $captcha_code)
{
if (empty($captcha_key) || empty($captcha_code)) {
return false;
}
$answer = cache('captcha_' . $captcha_key);
if ($answer === false || $answer === null) {
return false;
}
$result = (trim($captcha_code) === trim($answer));
// 验证后立即删除,防止重放
cache('captcha_' . $captcha_key, null);
return $result;
}
/**
* 生成随机key secrit
*/
public function set_key()
{
$str = "1231234567894567891234567890000qwertyuioplkjhgfdsazxcvbnm123456789qwertyuioplkjhgfdsazxcvbnmqwertyuioplkjhgfdsazxcvbnmqwertyuioplkjhgfdsazxcvbnm123456789";
$key = substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(4, 12));
$secret = substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(8, 10))."-".substr(str_shuffle($str), 0,rand(4, 12));
$data = array(
"key" => $key,
"secret" => $secret,
);
return $data;
}
}