diff --git a/library/com/.gitignore b/library/com/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/library/com/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/library/org/README.md b/library/org/README.md deleted file mode 100644 index 6f940733..00000000 --- a/library/org/README.md +++ /dev/null @@ -1 +0,0 @@ -Org命名空间目录 diff --git a/library/org/upload.php b/library/org/upload.php new file mode 100644 index 00000000..736c3a7b --- /dev/null +++ b/library/org/upload.php @@ -0,0 +1,453 @@ + +// +---------------------------------------------------------------------- +namespace org; + +class Upload +{ + /** + * 默认上传配置 + * @var array + */ + private $config = [ + 'mimes' => [], //允许上传的文件MiMe类型 + 'maxSize' => 0, //上传的文件大小限制 (0-不做限制) + 'exts' => [], //允许上传的文件后缀 + 'autoSub' => true, //自动子目录保存文件 + 'subName' => ['date', 'Y-m-d'], //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组 + 'rootPath' => './Uploads/', //保存根路径 + 'savePath' => '', //保存路径 + 'saveName' => ['uniqid', ''], //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组 + 'saveExt' => '', //文件保存后缀,空则使用原后缀 + 'replace' => false, //存在同名是否覆盖 + 'hash' => true, //是否生成hash编码 + 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组 + 'driver' => '', // 文件上传驱动 + 'driverConfig' => [], // 上传驱动配置 + ]; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * 上传驱动实例 + * @var Object + */ + private $uploader; + + /** + * 构造方法,用于构造上传实例 + * @param array $config 配置 + * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动,FTP-FTP上传驱动 + */ + public function __construct($config = [], $driver = '', $driverConfig = null) + { + /* 获取配置 */ + $this->config = array_merge($this->config, $config); + + /* 设置上传驱动 */ + $this->setDriver($driver, $driverConfig); + + /* 调整配置,把字符串配置参数转换为数组 */ + if (!empty($this->config['mimes'])) { + if (is_string($this->mimes)) { + $this->config['mimes'] = explode(',', $this->mimes); + } + $this->config['mimes'] = array_map('strtolower', $this->mimes); + } + if (!empty($this->config['exts'])) { + if (is_string($this->exts)) { + $this->config['exts'] = explode(',', $this->exts); + } + $this->config['exts'] = array_map('strtolower', $this->exts); + } + } + + /** + * 使用 $this->name 获取配置 + * @param string $name 配置名称 + * @return multitype 配置值 + */ + public function __get($name) + { + return $this->config[$name]; + } + + public function __set($name, $value) + { + if (isset($this->config[$name])) { + $this->config[$name] = $value; + if ('driverConfig' == $name) { + //改变驱动配置后重置上传驱动 + //注意:必须选改变驱动然后再改变驱动配置 + $this->setDriver(); + } + } + } + + public function __isset($name) + { + return isset($this->config[$name]); + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->error; + } + + /** + * 上传单个文件 + * @param array $file 文件数组 + * @return array 上传成功后的文件信息 + */ + public function uploadOne($file) + { + $info = $this->upload([$file]); + return $info ? $info[0] : $info; + } + + /** + * 上传文件 + * @param 文件信息数组 $files ,通常是 $_FILES数组 + */ + public function upload($files = '') + { + if ('' === $files) { + $files = $_FILES; + } + if (empty($files)) { + $this->error = '没有上传的文件!'; + return false; + } + + /* 检测上传根目录 */ + if (!$this->uploader->checkRootPath($this->rootPath)) { + $this->error = $this->uploader->getError(); + return false; + } + + /* 检查上传目录 */ + if (!$this->uploader->checkSavePath($this->savePath)) { + $this->error = $this->uploader->getError(); + return false; + } + + /* 逐个检测并上传文件 */ + $info = []; + if (function_exists('finfo_open')) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + } + // 对上传文件数组信息处理 + $files = $this->dealFiles($files); + foreach ($files as $key => $file) { + $file['name'] = strip_tags($file['name']); + if (!isset($file['key'])) { + $file['key'] = $key; + } + + /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */ + if (isset($finfo)) { + $file['type'] = finfo_file($finfo, $file['tmp_name']); + } + + /* 获取上传文件后缀,允许上传无后缀文件 */ + $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION); + + /* 文件上传检测 */ + if (!$this->check($file)) { + continue; + } + + /* 获取文件hash */ + if ($this->hash) { + $file['md5'] = md5_file($file['tmp_name']); + $file['sha1'] = sha1_file($file['tmp_name']); + } + + /* 调用回调函数检测文件是否存在 */ + $data = call_user_func($this->callback, $file); + if ($this->callback && $data) { + if (file_exists('.' . $data['path'])) { + $info[$key] = $data; + continue; + } elseif ($this->removeTrash) { + call_user_func($this->removeTrash, $data); //删除垃圾据 + } + } + + /* 生成保存文件名 */ + $savename = $this->getSaveName($file); + if (false == $savename) { + continue; + } else { + $file['savename'] = $savename; + } + + /* 检测并创建子目录 */ + $subpath = $this->getSubPath($file['name']); + if (false === $subpath) { + continue; + } else { + $file['savepath'] = $this->savePath . $subpath; + } + + /* 对图像文件进行严格检测 */ + $ext = strtolower($file['ext']); + if (in_array($ext, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'])) { + $imginfo = getimagesize($file['tmp_name']); + if (empty($imginfo) || ('gif' == $ext && empty($imginfo['bits']))) { + $this->error = '非法图像文件!'; + continue; + } + } + + /* 保存文件 并记录保存成功的文件 */ + if ($this->uploader->save($file, $this->replace)) { + unset($file['error'], $file['tmp_name']); + $info[$key] = $file; + } else { + $this->error = $this->uploader->getError(); + } + } + if (isset($finfo)) { + finfo_close($finfo); + } + return empty($info) ? false : $info; + } + + /** + * 转换上传文件数组变量为正确的方式 + * @access private + * @param array $files 上传的文件变量 + * @return array + */ + private function dealFiles($files) + { + $fileArray = []; + $n = 0; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i = 0; $i < $count; $i++) { + $fileArray[$n]['key'] = $key; + foreach ($keys as $_key) { + $fileArray[$n][$_key] = $file[$_key][$i]; + } + $n++; + } + } else { + $fileArray = $files; + break; + } + } + return $fileArray; + } + + /** + * 设置上传驱动 + * @param string $driver 驱动名称 + * @param array $config 驱动配置 + */ + private function setDriver($driver = null, $config = null) + { + $driver = $driver ?: $this->config['driver']; + $config = $config ?: $this->config['driver_config']; + $class = strpos($driver, '\\') ? $driver : '\\org\\upload\\driver\\' . ucfirst(strtolower($driver)); + $this->uploader = new $class($config); + if (!$this->uploader) { + E("不存在上传驱动:{$name}"); + } + } + + /** + * 检查上传的文件 + * @param array $file 文件信息 + */ + private function check($file) + { + /* 文件上传失败,捕获错误代码 */ + if ($file['error']) { + $this->error($file['error']); + return false; + } + + /* 无效上传 */ + if (empty($file['name'])) { + $this->error = '未知上传错误!'; + } + + /* 检查是否合法上传 */ + if (!is_uploaded_file($file['tmp_name'])) { + $this->error = '非法上传文件!'; + return false; + } + + /* 检查文件大小 */ + if (!$this->checkSize($file['size'])) { + $this->error = '上传文件大小不符!'; + return false; + } + + /* 检查文件Mime类型 */ + //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream + if (!$this->checkMime($file['type'])) { + $this->error = '上传文件MIME类型不允许!'; + return false; + } + + /* 检查文件后缀 */ + if (!$this->checkExt($file['ext'])) { + $this->error = '上传文件后缀不允许'; + return false; + } + + /* 通过检测 */ + return true; + } + + /** + * 获取错误代码信息 + * @param string $errorNo 错误号 + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!'; + break; + case 2: + $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!'; + break; + case 3: + $this->error = '文件只有部分被上传!'; + break; + case 4: + $this->error = '没有文件被上传!'; + break; + case 6: + $this->error = '找不到临时文件夹!'; + break; + case 7: + $this->error = '文件写入失败!'; + break; + default: + $this->error = '未知上传错误!'; + } + } + + /** + * 检查文件大小是否合法 + * @param integer $size 数据 + */ + private function checkSize($size) + { + return !($size > $this->maxSize) || (0 == $this->maxSize); + } + + /** + * 检查上传的文件MIME类型是否合法 + * @param string $mime 数据 + */ + private function checkMime($mime) + { + return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes); + } + + /** + * 检查上传的文件后缀是否合法 + * @param string $ext 后缀 + */ + private function checkExt($ext) + { + return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts); + } + + /** + * 根据上传文件命名规则取得保存文件名 + * @param string $file 文件信息 + */ + private function getSaveName($file) + { + $rule = $this->saveName; + if (empty($rule)) { + //保持文件名不变 + /* 解决pathinfo中文文件名BUG */ + $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1); + $savename = $filename; + } else { + $savename = $this->getName($rule, $file['name']); + if (empty($savename)) { + $this->error = '文件命名规则错误!'; + return false; + } + } + + /* 文件保存后缀,支持强制更改文件后缀 */ + $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt; + + return $savename . '.' . $ext; + } + + /** + * 获取子目录的名称 + * @param array $file 上传的文件信息 + */ + private function getSubPath($filename) + { + $subpath = ''; + $rule = $this->subName; + if ($this->autoSub && !empty($rule)) { + $subpath = $this->getName($rule, $filename) . '/'; + + if (!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)) { + $this->error = $this->uploader->getError(); + return false; + } + } + return $subpath; + } + + /** + * 根据指定的规则获取文件或目录名称 + * @param array $rule 规则 + * @param string $filename 原文件名 + * @return string 文件或目录名称 + */ + private function getName($rule, $filename) + { + $name = ''; + if (is_array($rule)) { + //数组规则 + $func = $rule[0]; + $param = (array) $rule[1]; + foreach ($param as &$value) { + $value = str_replace('__FILE__', $filename, $value); + } + $name = call_user_func_array($func, $param); + } elseif (is_string($rule)) { + //字符串规则 + if (function_exists($rule)) { + $name = call_user_func($rule); + } else { + $name = $rule; + } + } + return $name; + } + +} diff --git a/library/org/upload/driver/ftp.php b/library/org/upload/driver/ftp.php new file mode 100644 index 00000000..33d0a6b0 --- /dev/null +++ b/library/org/upload/driver/ftp.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- + +namespace org\upload\driver; + +use think\Exception; + +class Ftp +{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * FTP连接 + * @var resource + */ + private $link; + + private $config = [ + 'host' => '', //服务器 + 'port' => 21, //端口 + 'timeout' => 90, //超时时间 + 'username' => '', //用户名 + 'password' => '', //密码 + ]; + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config) + { + /* 默认FTP配置 */ + $this->config = array_merge($this->config, $config); + + /* 登录FTP服务器 */ + if (!$this->login()) { + throw new Exception($this->error); + } + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath) + { + /* 设置根目录 */ + $this->rootPath = ftp_pwd($this->link) . '/' . ltrim($rootpath, '/'); + + if (!@ftp_chdir($this->link, $this->rootPath)) { + $this->error = '上传根目录不存在!'; + return false; + } + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath) + { + /* 检测并创建目录 */ + if (!$this->mkdir($savepath)) { + return false; + } else { + //TODO:检测目录是否可写 + return true; + } + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace = true) + { + $filename = $this->rootPath . $file['savepath'] . $file['savename']; + + /* 不覆盖同名文件 */ + // if (!$replace && is_file($filename)) { + // $this->error = '存在同名文件' . $file['savename']; + // return false; + // } + + /* 移动文件 */ + if (!ftp_put($this->link, $filename, $file['tmp_name'], FTP_BINARY)) { + $this->error = '文件上传保存错误!'; + return false; + } + return true; + } + + /** + * 创建目录 + * @param string $savepath 要创建的目录 + * @return boolean 创建状态,true-成功,false-失败 + */ + public function mkdir($savepath) + { + $dir = $this->rootPath . $savepath; + if (ftp_chdir($this->link, $dir)) { + return true; + } + + if (ftp_mkdir($this->link, $dir)) { + return true; + } elseif ($this->mkdir(dirname($savepath)) && ftp_mkdir($this->link, $dir)) { + return true; + } else { + $this->error = "目录 {$savepath} 创建失败!"; + return false; + } + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->error; + } + + /** + * 登录到FTP服务器 + * @return boolean true-登录成功,false-登录失败 + */ + private function login() + { + extract($this->config); + $this->link = ftp_connect($host, $port, $timeout); + if ($this->link) { + if (ftp_login($this->link, $username, $password)) { + return true; + } else { + $this->error = "无法登录到FTP服务器:username - {$username}"; + } + } else { + $this->error = "无法连接到FTP服务器:{$host}"; + } + return false; + } + + /** + * 析构方法,用于断开当前FTP连接 + */ + public function __destruct() + { + ftp_close($this->link); + } + +} diff --git a/library/org/upload/driver/local.php b/library/org/upload/driver/local.php new file mode 100644 index 00000000..a6f11a12 --- /dev/null +++ b/library/org/upload/driver/local.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace org\upload\driver; + +class Local +{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; //上传错误信息 + + /** + * 构造函数,用于设置上传根路径 + */ + public function __construct($config = null) + { + + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath) + { + if (!(is_dir($rootpath) && is_writable($rootpath))) { + $this->error = '上传根目录不存在!请尝试手动创建:' . $rootpath; + return false; + } + $this->rootPath = $rootpath; + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath) + { + /* 检测并创建目录 */ + if (!$this->mkdir($savepath)) { + return false; + } else { + /* 检测目录是否可写 */ + if (!is_writable($this->rootPath . $savepath)) { + $this->error = '上传目录 ' . $savepath . ' 不可写!'; + return false; + } else { + return true; + } + } + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace = true) + { + $filename = $this->rootPath . $file['savepath'] . $file['savename']; + + /* 不覆盖同名文件 */ + if (!$replace && is_file($filename)) { + $this->error = '存在同名文件' . $file['savename']; + return false; + } + + /* 移动文件 */ + if (!move_uploaded_file($file['tmp_name'], $filename)) { + $this->error = '文件上传保存错误!'; + return false; + } + + return true; + } + + /** + * 创建目录 + * @param string $savepath 要创建的目录 + * @return boolean 创建状态,true-成功,false-失败 + */ + public function mkdir($savepath) + { + $dir = $this->rootPath . $savepath; + if (is_dir($dir)) { + return true; + } + + if (mkdir($dir, 0777, true)) { + return true; + } else { + $this->error = "目录 {$savepath} 创建失败!"; + return false; + } + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->error; + } + +} diff --git a/library/org/upload/driver/qiniu.php b/library/org/upload/driver/qiniu.php new file mode 100644 index 00000000..32ce4a88 --- /dev/null +++ b/library/org/upload/driver/qiniu.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace org\upload\driver; + +use org\upload\driver\qiniu\QiniuStorage; + +class Qiniu +{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; + + private $config = [ + 'secretKey' => '', //七牛服务器 + 'accessKey' => '', //七牛用户 + 'domain' => '', //七牛密码 + 'bucket' => '', //空间名称 + 'timeout' => 300, //超时时间 + ]; + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config) + { + $this->config = array_merge($this->config, $config); + /* 设置根目录 */ + $this->qiniu = new QiniuStorage($config); + } + + /** + * 检测上传根目录(七牛上传时支持自动创建目录,直接返回) + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath) + { + $this->rootPath = trim($rootpath, './') . '/'; + return true; + } + + /** + * 检测上传目录(七牛上传时支持自动创建目录,直接返回) + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath) + { + return true; + } + + /** + * 创建文件夹 (七牛上传时支持自动创建目录,直接返回) + * @param string $savepath 目录名称 + * @return boolean true-创建成功,false-创建失败 + */ + public function mkdir($savepath) + { + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save(&$file, $replace = true) + { + $file['name'] = $file['savepath'] . $file['savename']; + $key = str_replace('/', '_', $file['name']); + $upfile = [ + 'name' => 'file', + 'fileName' => $key, + 'fileBody' => file_get_contents($file['tmp_name']), + ]; + $config = []; + $result = $this->qiniu->upload($config, $upfile); + $url = $this->qiniu->downlink($key); + $file['url'] = $url; + return false === $result ? false : true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->qiniu->errorStr; + } +} diff --git a/library/org/upload/driver/qiniu/qiniu_storage.php b/library/org/upload/driver/qiniu/qiniu_storage.php new file mode 100644 index 00000000..8f8706c4 --- /dev/null +++ b/library/org/upload/driver/qiniu/qiniu_storage.php @@ -0,0 +1,366 @@ +sk = $config['secretKey']; + $this->ak = $config['accessKey']; + $this->domain = $config['domain']; + $this->bucket = $config['bucket']; + $this->timeout = isset($config['timeout']) ? $config['timeout'] : 3600; + } + + public static function sign($sk, $ak, $data) + { + $sign = hash_hmac('sha1', $data, $sk, true); + return $ak . ':' . self::qiniuEncode($sign); + } + + public static function signWithData($sk, $ak, $data) + { + $data = self::qiniuEncode($data); + return self::sign($sk, $ak, $data) . ':' . $data; + } + + public function accessToken($url, $body = '') + { + $parsed_url = parse_url($url); + $path = $parsed_url['path']; + $access = $path; + if (isset($parsed_url['query'])) { + $access .= "?" . $parsed_url['query']; + } + $access .= "\n"; + + if ($body) { + $access .= $body; + } + return self::sign($this->sk, $this->ak, $access); + } + + public function UploadToken($sk, $ak, $param) + { + $param['deadline'] = 0 == $param['Expires'] ? 3600 : $param['Expires']; + $param['deadline'] += time(); + $data = array('scope' => $this->bucket, 'deadline' => $param['deadline']); + if (!empty($param['CallbackUrl'])) { + $data['callbackUrl'] = $param['CallbackUrl']; + } + if (!empty($param['CallbackBody'])) { + $data['callbackBody'] = $param['CallbackBody']; + } + if (!empty($param['ReturnUrl'])) { + $data['returnUrl'] = $param['ReturnUrl']; + } + if (!empty($param['ReturnBody'])) { + $data['returnBody'] = $param['ReturnBody']; + } + if (!empty($param['AsyncOps'])) { + $data['asyncOps'] = $param['AsyncOps']; + } + if (!empty($param['EndUser'])) { + $data['endUser'] = $param['EndUser']; + } + $data = json_encode($data); + return self::SignWithData($sk, $ak, $data); + } + + public function upload($config, $file) + { + $uploadToken = $this->UploadToken($this->sk, $this->ak, $config); + + $url = "{$this->QINIU_UP_HOST}"; + $mimeBoundary = md5(microtime()); + $header = array('Content-Type' => 'multipart/form-data;boundary=' . $mimeBoundary); + $data = array(); + + $fields = array( + 'token' => $uploadToken, + 'key' => $config['saveName'] ?: $file['fileName'], + ); + + if (is_array($config['custom_fields']) && array() !== $config['custom_fields']) { + $fields = array_merge($fields, $config['custom_fields']); + } + + foreach ($fields as $name => $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$name\""); + array_push($data, ''); + array_push($data, $val); + } + + //文件 + array_push($data, '--' . $mimeBoundary); + $name = $file['name']; + $fileName = $file['fileName']; + $fileBody = $file['fileBody']; + $fileName = self::qiniuEscapequotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$fileName\""); + array_push($data, 'Content-Type: application/octet-stream'); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + $response = $this->request($url, 'POST', $header, $body); + return $response; + } + + public function dealWithType($key, $type) + { + $param = $this->buildUrlParam(); + $url = ''; + + switch ($type) { + case 'img': + $url = $this->downLink($key); + if ($param['imageInfo']) { + $url .= '?imageInfo'; + } else if ($param['exif']) { + $url .= '?exif'; + } else if ($param['imageView']) { + $url .= '?imageView/' . $param['mode']; + if ($param['w']) { + $url .= "/w/{$param['w']}"; + } + + if ($param['h']) { + $url .= "/h/{$param['h']}"; + } + + if ($param['q']) { + $url .= "/q/{$param['q']}"; + } + + if ($param['format']) { + $url .= "/format/{$param['format']}"; + } + + } + break; + case 'video'://TODO 视频处理 + case 'doc': + $url = $this->downLink($key); + $url .= '?md2html'; + if (isset($param['mode'])) { + $url .= '/' . (int) $param['mode']; + } + + if ($param['cssurl']) { + $url .= '/' . self::qiniuEncode($param['cssurl']); + } + + break; + + } + return $url; + } + + public function buildUrlParam() + { + return $_REQUEST; + } + + //获取某个路径下的文件列表 + public function getList($query = array(), $path = '') + { + $query = array_merge(array('bucket' => $this->bucket), $query); + $url = "{$this->QINIU_RSF_HOST}/list?" . http_build_query($query); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken")); + return $response; + } + + //获取某个文件的信息 + public function info($key) + { + $key = trim($key); + $url = "{$this->QINIU_RS_HOST}/stat/" . self::qiniuEncode("{$this->bucket}:{$key}"); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array( + 'Authorization' => "QBox $accessToken", + )); + return $response; + } + + //获取文件下载资源链接 + public function downLink($key) + { + $key = urlencode($key); + $key = self::qiniuEscapequotes($key); + $url = "http://{$this->domain}/{$key}"; + return $url; + } + + //重命名单个文件 + public function rename($file, $new_file) + { + $key = trim($file); + $url = "{$this->QINIU_RS_HOST}/move/" . self::qiniuEncode("{$this->bucket}:{$key}") . '/' . self::qiniuEncode("{$this->bucket}:{$new_file}"); + trace($url); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken")); + return $response; + } + + //删除单个文件 + public function del($file) + { + $key = trim($file); + $url = "{$this->QINIU_RS_HOST}/delete/" . self::qiniuEncode("{$this->bucket}:{$key}"); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken")); + return $response; + } + + //批量删除文件 + public function delBatch($files) + { + $url = $this->QINIU_RS_HOST . '/batch'; + $ops = array(); + foreach ($files as $file) { + $ops[] = "/delete/" . self::qiniuEncode("{$this->bucket}:{$file}"); + } + $params = 'op=' . implode('&op=', $ops); + $url .= '?' . $params; + trace($url); + $accessToken = $this->accessToken($url); + $response = $this->request($url, 'POST', array('Authorization' => "QBox $accessToken")); + return $response; + } + + public static function qiniuEncode($str) + { +// URLSafeBase64Encode + $find = array('+', '/'); + $replace = array('-', '_'); + return str_replace($find, $replace, base64_encode($str)); + } + + public static function qiniuEscapequotes($str) + { + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } + + /** + * 请求云服务器 + * @param string $path 请求的PATH + * @param string $method 请求方法 + * @param array $headers 请求header + * @param resource $body 上传文件资源 + * @return boolean + */ + private function request($path, $method, $headers = null, $body = null) + { + $ch = curl_init($path); + + $_headers = array('Expect:'); + if (!is_null($headers) && is_array($headers)) { + foreach ($headers as $k => $v) { + array_push($_headers, "{$k}: {$v}"); + } + } + + $length = 0; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + + if (!is_null($body)) { + if (is_resource($body)) { + fseek($body, 0, SEEK_END); + $length = ftell($body); + fseek($body, 0); + + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_INFILE, $body); + curl_setopt($ch, CURLOPT_INFILESIZE, $length); + } else { + $length = @strlen($body); + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + array_push($_headers, "Content-Length: {$length}"); + } + + // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length)); + array_push($_headers, "Date: {$date}"); + + curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ('PUT' == $method || 'POST' == $method) { + curl_setopt($ch, CURLOPT_POST, 1); + } else { + curl_setopt($ch, CURLOPT_POST, 0); + } + + if ('HEAD' == $method) { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + $response = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + list($header, $body) = explode("\r\n\r\n", $response, 2); + if (200 == $status) { + if ('GET' == $method) { + return $body; + } else { + return $this->response($response); + } + } else { + $this->error($header, $body); + return false; + } + } + + /** + * 获取响应数据 + * @param string $text 响应头字符串 + * @return array 响应数据列表 + */ + private function response($text) + { + $headers = explode(PHP_EOL, $text); + $items = array(); + foreach ($headers as $header) { + $header = trim($header); + if (strpos($header, '{') !== false) { + $items = json_decode($header, 1); + break; + } + } + return $items; + } + + /** + * 获取请求错误信息 + * @param string $header 请求返回头信息 + */ + private function error($header, $body) + { + list($status, $stash) = explode("\r\n", $header, 2); + list($v, $code, $message) = explode(" ", $status, 3); + $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}]"; + $this->error = $message; + $this->errorStr = json_decode($body, 1); + $this->errorStr = $this->errorStr['error']; + } +} diff --git a/library/org/upload/driver/sae.php b/library/org/upload/driver/sae.php new file mode 100644 index 00000000..af924bc2 --- /dev/null +++ b/library/org/upload/driver/sae.php @@ -0,0 +1,114 @@ + +// +---------------------------------------------------------------------- + +namespace org\upload\driver; + +class Sae +{ + /** + * Storage的Domain + * @var string + */ + private $domain = ''; + + private $rootPath = ''; + + /** + * 本地上传错误信息 + * @var string + */ + private $error = ''; + + /** + * 构造函数,设置storage的domain, 如果有传配置,则domain为配置项,如果没有传domain为第一个路径的目录名称。 + * @param mixed $config 上传配置 + */ + public function __construct($config = null) + { + if (is_array($config) && !empty($config['domain'])) { + $this->domain = strtolower($config['domain']); + } + } + + /** + * 检测上传根目录 + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath) + { + $rootpath = trim($rootpath, './'); + if (!$this->domain) { + $rootpath = explode('/', $rootpath); + $this->domain = strtolower(array_shift($rootpath)); + $rootpath = implode('/', $rootpath); + } + + $this->rootPath = $rootpath; + $st = new \SaeStorage(); + if (false === $st->getDomainCapacity($this->domain)) { + $this->error = '您好像没有建立Storage的domain[' . $this->domain . ']'; + return false; + } + return true; + } + + /** + * 检测上传目录 + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath) + { + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save(&$file, $replace = true) + { + $filename = ltrim($this->rootPath . '/' . $file['savepath'] . $file['savename'], '/'); + $st = new \SaeStorage(); + /* 不覆盖同名文件 */ + if (!$replace && $st->fileExists($this->domain, $filename)) { + $this->error = '存在同名文件' . $file['savename']; + return false; + } + + /* 移动文件 */ + if (!$st->upload($this->domain, $filename, $file['tmp_name'])) { + $this->error = '文件上传保存错误![' . $st->errno() . ']:' . $st->errmsg(); + return false; + } else { + $file['url'] = $st->getUrl($this->domain, $filename); + } + return true; + } + + public function mkdir() + { + return true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->error; + } + +} diff --git a/library/org/upload/driver/upyun.php b/library/org/upload/driver/upyun.php new file mode 100644 index 00000000..c52a0e69 --- /dev/null +++ b/library/org/upload/driver/upyun.php @@ -0,0 +1,230 @@ + +// +---------------------------------------------------------------------- + +namespace org\upload\driver; + +class Upyun +{ + /** + * 上传文件根目录 + * @var string + */ + private $rootPath; + + /** + * 上传错误信息 + * @var string + */ + private $error = ''; + + private $config = [ + 'host' => '', //又拍云服务器 + 'username' => '', //又拍云用户 + 'password' => '', //又拍云密码 + 'bucket' => '', //空间名称 + 'timeout' => 90, //超时时间 + ]; + + /** + * 构造函数,用于设置上传根路径 + * @param array $config FTP配置 + */ + public function __construct($config) + { + /* 默认FTP配置 */ + $this->config = array_merge($this->config, $config); + $this->config['password'] = md5($this->config['password']); + } + + /** + * 检测上传根目录(又拍云上传时支持自动创建目录,直接返回) + * @param string $rootpath 根目录 + * @return boolean true-检测通过,false-检测失败 + */ + public function checkRootPath($rootpath) + { + /* 设置根目录 */ + $this->rootPath = trim($rootpath, './') . '/'; + return true; + } + + /** + * 检测上传目录(又拍云上传时支持自动创建目录,直接返回) + * @param string $savepath 上传目录 + * @return boolean 检测结果,true-通过,false-失败 + */ + public function checkSavePath($savepath) + { + return true; + } + + /** + * 创建文件夹 (又拍云上传时支持自动创建目录,直接返回) + * @param string $savepath 目录名称 + * @return boolean true-创建成功,false-创建失败 + */ + public function mkdir($savepath) + { + return true; + } + + /** + * 保存指定文件 + * @param array $file 保存的文件信息 + * @param boolean $replace 同名文件是否覆盖 + * @return boolean 保存状态,true-成功,false-失败 + */ + public function save($file, $replace = true) + { + $header['Content-Type'] = $file['type']; + $header['Content-MD5'] = $file['md5']; + $header['Mkdir'] = 'true'; + $resource = fopen($file['tmp_name'], 'r'); + + $save = $this->rootPath . $file['savepath'] . $file['savename']; + $data = $this->request($save, 'PUT', $header, $resource); + return false === $data ? false : true; + } + + /** + * 获取最后一次上传错误信息 + * @return string 错误信息 + */ + public function getError() + { + return $this->error; + } + + /** + * 请求又拍云服务器 + * @param string $path 请求的PATH + * @param string $method 请求方法 + * @param array $headers 请求header + * @param resource $body 上传文件资源 + * @return boolean + */ + private function request($path, $method, $headers = null, $body = null) + { + $uri = "/{$this->config['bucket']}/{$path}"; + $ch = curl_init($this->config['host'] . $uri); + + $_headers = ['Expect:']; + if (!is_null($headers) && is_array($headers)) { + foreach ($headers as $k => $v) { + array_push($_headers, "{$k}: {$v}"); + } + } + + $length = 0; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + + if (!is_null($body)) { + if (is_resource($body)) { + fseek($body, 0, SEEK_END); + $length = ftell($body); + fseek($body, 0); + + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_INFILE, $body); + curl_setopt($ch, CURLOPT_INFILESIZE, $length); + } else { + $length = @strlen($body); + array_push($_headers, "Content-Length: {$length}"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + array_push($_headers, "Content-Length: {$length}"); + } + + array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length)); + array_push($_headers, "Date: {$date}"); + + curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->config['timeout']); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + + if ('PUT' == $method || 'POST' == $method) { + curl_setopt($ch, CURLOPT_POST, 1); + } else { + curl_setopt($ch, CURLOPT_POST, 0); + } + + if ('HEAD' == $method) { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + $response = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + list($header, $body) = explode("\r\n\r\n", $response, 2); + + if (200 == $status) { + if ('GET' == $method) { + return $body; + } else { + $data = $this->response($header); + return count($data) > 0 ? $data : true; + } + } else { + $this->error($header); + return false; + } + } + + /** + * 获取响应数据 + * @param string $text 响应头字符串 + * @return array 响应数据列表 + */ + private function response($text) + { + $headers = explode("\r\n", $text); + $items = []; + foreach ($headers as $header) { + $header = trim($header); + if (strpos($header, 'x-upyun') !== false) { + list($k, $v) = explode(':', $header); + $items[trim($k)] = in_array(substr($k, 8, 5), ['width', 'heigh', 'frame']) ? intval($v) : trim($v); + } + } + return $items; + } + + /** + * 生成请求签名 + * @param string $method 请求方法 + * @param string $uri 请求URI + * @param string $date 请求时间 + * @param integer $length 请求内容大小 + * @return string 请求签名 + */ + private function sign($method, $uri, $date, $length) + { + $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->config['password']}"; + return 'UpYun ' . $this->config['username'] . ':' . md5($sign); + } + + /** + * 获取请求错误信息 + * @param string $header 请求返回头信息 + */ + private function error($header) + { + list($status, $stash) = explode("\r\n", $header, 2); + list($v, $code, $message) = explode(" ", $status, 3); + $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}"; + $this->error = $message; + } + +} diff --git a/library/org/verify.php b/library/org/verify.php new file mode 100644 index 00000000..744ba49f --- /dev/null +++ b/library/org/verify.php @@ -0,0 +1,305 @@ + +// +---------------------------------------------------------------------- + +namespace org; + +class Verify +{ + protected $config = [ + 'seKey' => 'ThinkPHP.CN', // 验证码加密密钥 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', // 验证码字符集合 + 'expire' => 1800, // 验证码过期时间(s) + 'useZh' => false, // 使用中文验证码 + 'zhSet' => '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借', // 中文验证码字符串 + 'useImgBg' => false, // 使用背景图片 + 'fontSize' => 25, // 验证码字体大小(px) + 'useCurve' => true, // 是否画混淆曲线 + 'useNoise' => true, // 是否添加杂点 + 'imageH' => 0, // 验证码图片高度 + 'imageW' => 0, // 验证码图片宽度 + 'length' => 5, // 验证码位数 + 'fontttf' => '', // 验证码字体,不设置随机获取 + 'bg' => [243, 251, 254], // 背景颜色 + 'reset' => true, // 验证成功后是否重置 + ]; + + private $_image = null; // 验证码图片实例 + private $_color = null; // 验证码字体颜色 + + /** + * 架构方法 设置参数 + * @access public + * @param array $config 配置参数 + */ + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 使用 $this->name 获取配置 + * @access public + * @param string $name 配置名称 + * @return multitype 配置值 + */ + public function __get($name) + { + return $this->config[$name]; + } + + /** + * 设置验证码配置 + * @access public + * @param string $name 配置名称 + * @param string $value 配置值 + * @return void + */ + public function __set($name, $value) + { + if (isset($this->config[$name])) { + $this->config[$name] = $value; + } + } + + /** + * 检查配置 + * @access public + * @param string $name 配置名称 + * @return bool + */ + public function __isset($name) + { + return isset($this->config[$name]); + } + + /** + * 验证验证码是否正确 + * @access public + * @param string $code 用户验证码 + * @param string $id 验证码标识 + * @return bool 用户验证码是否正确 + */ + public function check($code, $id = '') + { + $key = $this->authcode($this->seKey) . $id; + // 验证码不能为空 + $secode = session($key); + if (empty($code) || empty($secode)) { + return false; + } + // session 过期 + if (NOW_TIME - $secode['verify_time'] > $this->expire) { + session($key, null); + return false; + } + + if ($this->authcode(strtoupper($code)) == $secode['verify_code']) { + $this->reset && session($key, null); + return true; + } + + return false; + } + + /** + * 输出验证码并把验证码的值保存的session中 + * 验证码保存到session的格式为: array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间'); + * @access public + * @param string $id 要生成验证码的标识 + * @return void + */ + public function entry($id = '') + { + // 图片宽(px) + $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2; + // 图片高(px) + $this->imageH || $this->imageH = $this->fontSize * 2.5; + // 建立一幅 $this->imageW x $this->imageH 的图像 + $this->_image = imagecreate($this->imageW, $this->imageH); + // 设置背景 + imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]); + + // 验证码字体随机颜色 + $this->_color = imagecolorallocate($this->_image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150)); + // 验证码使用随机字体 + $ttfPath = dirname(__FILE__) . '/Verify/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; + + if (empty($this->fontttf)) { + $dir = dir($ttfPath); + $ttfs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.ttf') { + $ttfs[] = $file; + } + } + $dir->close(); + $this->fontttf = $ttfs[array_rand($ttfs)]; + } + $this->fontttf = $ttfPath . $this->fontttf; + + if ($this->useImgBg) { + $this->_background(); + } + + if ($this->useNoise) { + // 绘杂点 + $this->_writeNoise(); + } + if ($this->useCurve) { + // 绘干扰线 + $this->_writeCurve(); + } + + // 绘验证码 + $code = []; // 验证码 + $codeNX = 0; // 验证码第N个字符的左边距 + if ($this->useZh) { + // 中文验证码 + for ($i = 0; $i < $this->length; $i++) { + $code[$i] = iconv_substr($this->zhSet, floor(mt_rand(0, mb_strlen($this->zhSet, 'utf-8') - 1)), 1, 'utf-8'); + imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $this->fontSize * ($i + 1) * 1.5, $this->fontSize + mt_rand(10, 20), $this->_color, $this->fontttf, $code[$i]); + } + } else { + for ($i = 0; $i < $this->length; $i++) { + $code[$i] = $this->codeSet[mt_rand(0, strlen($this->codeSet) - 1)]; + $codeNX += mt_rand($this->fontSize * 1.2, $this->fontSize * 1.6); + imagettftext($this->_image, $this->fontSize, mt_rand(-40, 40), $codeNX, $this->fontSize * 1.6, $this->_color, $this->fontttf, $code[$i]); + } + } + + // 保存验证码 + $key = $this->authcode($this->seKey); + $code = $this->authcode(strtoupper(implode('', $code))); + $secode = []; + $secode['verify_code'] = $code; // 把校验码保存到session + $secode['verify_time'] = NOW_TIME; // 验证码创建时间 + session($key . $id, $secode); + + header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate'); + header('Cache-Control: post-check=0, pre-check=0', false); + header('Pragma: no-cache'); + header("content-type: image/png"); + + // 输出图像 + imagepng($this->_image); + imagedestroy($this->_image); + } + + /** + * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) + * + * 高中的数学公式咋都忘了涅,写出来 + * 正弦型函数解析式:y=Asin(ωx+φ)+b + * 各常数值对函数图像的影响: + * A:决定峰值(即纵向拉伸压缩的倍数) + * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) + * φ:决定波形与X轴位置关系或横向移动距离(左加右减) + * ω:决定周期(最小正周期T=2π/∣ω∣) + * + */ + private function _writeCurve() + { + $px = $py = 0; + + // 曲线前部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + + $px1 = 0; // 曲线横坐标起始位置 + $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置 + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 + $i--; + } + } + } + + // 曲线后部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2; + $px1 = $px2; + $px2 = $this->imageW; + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->_image, $px + $i, $py + $i, $this->_color); + $i--; + } + } + } + } + + /** + * 画杂点 + * 往图片上写不同颜色的字母或数字 + */ + private function _writeNoise() + { + $codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; + for ($i = 0; $i < 10; $i++) { + //杂点颜色 + $noiseColor = imagecolorallocate($this->_image, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225)); + for ($j = 0; $j < 5; $j++) { + // 绘杂点 + imagestring($this->_image, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); + } + } + } + + /** + * 绘制背景图片 + * 注:如果验证码输出图片比较大,将占用比较多的系统资源 + */ + private function _background() + { + $path = dirname(__FILE__) . '/Verify/bgs/'; + $dir = dir($path); + + $bgs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.jpg') { + $bgs[] = $path . $file; + } + } + $dir->close(); + + $gb = $bgs[array_rand($bgs)]; + + list($width, $height) = @getimagesize($gb); + // Resample + $bgImage = @imagecreatefromjpeg($gb); + @imagecopyresampled($this->_image, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); + @imagedestroy($bgImage); + } + + /* 加密验证码 */ + private function authcode($str) + { + $key = substr(md5($this->seKey), 5, 8); + $str = substr(md5($str), 8, 10); + return md5($key . $str); + } + +} diff --git a/library/org/verify/bgs/1.jpg b/library/org/verify/bgs/1.jpg new file mode 100644 index 00000000..d417136b Binary files /dev/null and b/library/org/verify/bgs/1.jpg differ diff --git a/library/org/verify/bgs/2.jpg b/library/org/verify/bgs/2.jpg new file mode 100644 index 00000000..56640bde Binary files /dev/null and b/library/org/verify/bgs/2.jpg differ diff --git a/library/org/verify/bgs/3.jpg b/library/org/verify/bgs/3.jpg new file mode 100644 index 00000000..83e5bd90 Binary files /dev/null and b/library/org/verify/bgs/3.jpg differ diff --git a/library/org/verify/bgs/4.jpg b/library/org/verify/bgs/4.jpg new file mode 100644 index 00000000..97a3721b Binary files /dev/null and b/library/org/verify/bgs/4.jpg differ diff --git a/library/org/verify/bgs/5.jpg b/library/org/verify/bgs/5.jpg new file mode 100644 index 00000000..220a17a2 Binary files /dev/null and b/library/org/verify/bgs/5.jpg differ diff --git a/library/org/verify/bgs/6.jpg b/library/org/verify/bgs/6.jpg new file mode 100644 index 00000000..be53ea0a Binary files /dev/null and b/library/org/verify/bgs/6.jpg differ diff --git a/library/org/verify/bgs/7.jpg b/library/org/verify/bgs/7.jpg new file mode 100644 index 00000000..fbf537fa Binary files /dev/null and b/library/org/verify/bgs/7.jpg differ diff --git a/library/org/verify/bgs/8.jpg b/library/org/verify/bgs/8.jpg new file mode 100644 index 00000000..e10cf281 Binary files /dev/null and b/library/org/verify/bgs/8.jpg differ diff --git a/library/org/verify/ttfs/1.ttf b/library/org/verify/ttfs/1.ttf new file mode 100644 index 00000000..d4ee1558 Binary files /dev/null and b/library/org/verify/ttfs/1.ttf differ diff --git a/library/org/verify/ttfs/2.ttf b/library/org/verify/ttfs/2.ttf new file mode 100644 index 00000000..3a452b68 Binary files /dev/null and b/library/org/verify/ttfs/2.ttf differ diff --git a/library/org/verify/ttfs/3.ttf b/library/org/verify/ttfs/3.ttf new file mode 100644 index 00000000..d07a4d93 Binary files /dev/null and b/library/org/verify/ttfs/3.ttf differ diff --git a/library/org/verify/ttfs/4.ttf b/library/org/verify/ttfs/4.ttf new file mode 100644 index 00000000..54a14ed1 Binary files /dev/null and b/library/org/verify/ttfs/4.ttf differ diff --git a/library/org/verify/ttfs/5.ttf b/library/org/verify/ttfs/5.ttf new file mode 100644 index 00000000..d672876d Binary files /dev/null and b/library/org/verify/ttfs/5.ttf differ diff --git a/library/org/verify/ttfs/6.ttf b/library/org/verify/ttfs/6.ttf new file mode 100644 index 00000000..7f183e20 Binary files /dev/null and b/library/org/verify/ttfs/6.ttf differ diff --git a/library/org/verify/zhttfs/1.ttf b/library/org/verify/zhttfs/1.ttf new file mode 100644 index 00000000..1c14f7fa Binary files /dev/null and b/library/org/verify/zhttfs/1.ttf differ