chore: 添加静态资源文件和版本配置文件
添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材 添加系统版本配置文件(.version)和常量定义文件(.constant) 更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件 添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
113
crmeb/services/upload/BaseClient.php
Normal file
113
crmeb/services/upload/BaseClient.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload;
|
||||
|
||||
/**
|
||||
* 基础请求
|
||||
* Class BaseClient
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
* @package crmeb\services\upload
|
||||
*/
|
||||
abstract class BaseClient
|
||||
{
|
||||
|
||||
/**
|
||||
* 是否解析为xml
|
||||
* @var bool
|
||||
*/
|
||||
protected $isXml = true;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var []callable
|
||||
*/
|
||||
protected $curlFn = [];
|
||||
|
||||
/**
|
||||
* @param callable $curlFn
|
||||
* @return $this
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function middleware(callable $curlFn)
|
||||
{
|
||||
$this->curlFn[] = $curlFn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $data
|
||||
* @param array $clientHeader
|
||||
* @param int $timeout
|
||||
* @return array|bool|SimpleXMLElement|mixed
|
||||
*/
|
||||
protected function requestClient(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10)
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($clientHeader as $key => $item) {
|
||||
$headers[] = $key . ':' . $item;
|
||||
}
|
||||
$curl = curl_init($url);
|
||||
//请求方式
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||
//post请求
|
||||
if (!empty($data['body'])) {
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data['body']);
|
||||
} else if (!empty($data['json'])) {
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data['json']));
|
||||
} else {
|
||||
$curlFn = $this->curlFn;
|
||||
foreach ($curlFn as $item) {
|
||||
if ($item instanceof \Closure) {
|
||||
$curlFn($curl);
|
||||
}
|
||||
}
|
||||
}
|
||||
//超时时间
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
|
||||
//设置header头
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
curl_setopt($curl, CURLOPT_FAILONERROR, false);
|
||||
//返回抓取数据
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
//输出header头信息
|
||||
curl_setopt($curl, CURLOPT_HEADER, true);
|
||||
//TRUE 时追踪句柄的请求字符串,从 PHP 5.1.3 开始可用。这个很关键,就是允许你查看请求header
|
||||
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
|
||||
//https请求
|
||||
if (1 == strpos("$" . $url, "https://")) {
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
||||
}
|
||||
[$content, $status] = [curl_exec($curl), curl_getinfo($curl)];
|
||||
curl_close($curl);
|
||||
$content = trim(substr($content, $status['header_size']));
|
||||
if ($content) {
|
||||
if ($this->isXml) {
|
||||
return XML::parse($content);
|
||||
} else {
|
||||
return json_decode($content, true);
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
crmeb/services/upload/Upload.php
Normal file
46
crmeb/services/upload/Upload.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\upload;
|
||||
|
||||
use think\facade\Config;
|
||||
use crmeb\basic\BaseManager;
|
||||
use crmeb\services\upload\storage\COS;
|
||||
use crmeb\services\upload\storage\Local;
|
||||
use crmeb\services\upload\storage\OSS;
|
||||
use crmeb\services\upload\storage\Qiniu;
|
||||
|
||||
/**
|
||||
* Class Upload
|
||||
* @package crmeb\services\upload
|
||||
* @mixin Local
|
||||
* @mixin OSS
|
||||
* @mixin COS
|
||||
* @mixin Qiniu
|
||||
*/
|
||||
class Upload extends BaseManager
|
||||
{
|
||||
/**
|
||||
* 空间名
|
||||
* @var string
|
||||
*/
|
||||
protected string $namespace = '\\crmeb\\services\\upload\\storage\\';
|
||||
|
||||
/**
|
||||
* 设置默认上传类型
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDefaultDriver()
|
||||
{
|
||||
return Config::get('upload.default', 'local');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
154
crmeb/services/upload/XML.php
Normal file
154
crmeb/services/upload/XML.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace crmeb\services\upload;
|
||||
|
||||
class XML
|
||||
{
|
||||
/**
|
||||
* XML to array.
|
||||
*
|
||||
* @param string $xml XML string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parse($xml)
|
||||
{
|
||||
$backup = PHP_MAJOR_VERSION < 8 ? libxml_disable_entity_loader(true) : null;
|
||||
|
||||
$result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS));
|
||||
|
||||
PHP_MAJOR_VERSION < 8 && libxml_disable_entity_loader($backup);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML encode.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param string $root
|
||||
* @param string $item
|
||||
* @param string $attr
|
||||
* @param string $id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function build(
|
||||
$data,
|
||||
$root = 'xml',
|
||||
$item = 'item',
|
||||
$attr = '',
|
||||
$id = 'id'
|
||||
)
|
||||
{
|
||||
if (is_array($attr)) {
|
||||
$_attr = [];
|
||||
|
||||
foreach ($attr as $key => $value) {
|
||||
$_attr[] = "{$key}=\"{$value}\"";
|
||||
}
|
||||
|
||||
$attr = implode(' ', $_attr);
|
||||
}
|
||||
|
||||
$attr = trim($attr);
|
||||
$attr = empty($attr) ? '' : " {$attr}";
|
||||
$xml = "<{$root}{$attr}>";
|
||||
$xml .= self::data2Xml($data, $item, $id);
|
||||
$xml .= "</{$root}>";
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CDATA.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function cdata($string)
|
||||
{
|
||||
return sprintf('<![CDATA[%s]]>', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object to array.
|
||||
*
|
||||
*
|
||||
* @param SimpleXMLElement $obj
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function normalize($obj)
|
||||
{
|
||||
$result = null;
|
||||
|
||||
if (is_object($obj)) {
|
||||
$obj = (array)$obj;
|
||||
}
|
||||
|
||||
if (is_array($obj)) {
|
||||
foreach ($obj as $key => $value) {
|
||||
$res = self::normalize($value);
|
||||
if (('@attributes' === $key) && ($key)) {
|
||||
$result = $res; // @codeCoverageIgnore
|
||||
} else {
|
||||
$result[$key] = $res;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result = $obj;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array to XML.
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $item
|
||||
* @param string $id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function data2Xml($data, $item = 'item', $id = 'id')
|
||||
{
|
||||
$xml = $attr = '';
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
$id && $attr = " {$id}=\"{$key}\"";
|
||||
$key = $item;
|
||||
}
|
||||
|
||||
$xml .= "<{$key}{$attr}>";
|
||||
|
||||
if ((is_array($val) || is_object($val))) {
|
||||
$xml .= self::data2Xml((array)$val, $item, $id);
|
||||
} else {
|
||||
$xml .= is_numeric($val) ? $val : self::cdata($val);
|
||||
}
|
||||
|
||||
$xml .= "</{$key}>";
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete invalid characters in XML.
|
||||
*
|
||||
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range
|
||||
* @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode
|
||||
*
|
||||
* @param string $xml
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitize($xml)
|
||||
{
|
||||
return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml);
|
||||
}
|
||||
}
|
||||
576
crmeb/services/upload/extend/cos/Client.php
Normal file
576
crmeb/services/upload/extend/cos/Client.php
Normal file
@@ -0,0 +1,576 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\cos;
|
||||
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\wechat\util\XML;
|
||||
|
||||
/**
|
||||
* Class Client
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
* @package crmeb\services\upload\extend\cos
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $appid;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
protected $bucket;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $response = ['content' => null, 'code' => 200, 'header' => []];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $request = ['header' => [], 'body' => [], 'host' => ''];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cosacl = 'public-read';
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->accessKey = $config['accessKey'] ?? '';
|
||||
$this->secretKey = $config['secretKey'] ?? '';
|
||||
$this->appid = $config['appid'] ?? '';
|
||||
$this->bucket = $config['bucket'] ?? '';
|
||||
$this->region = $config['region'] ?? 'ap-chengdu';
|
||||
$this->uploadUrl = $config['uploadUrl'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实际请求
|
||||
* @return array
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
$response = $this->response;
|
||||
|
||||
$this->response = ['content' => null, 'http_code' => 200, 'header' => []];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
$request = $this->request;
|
||||
|
||||
$this->request = ['header' => [], 'body' => [], 'host' => ''];
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接请求地址
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
protected function makeUpUrl()
|
||||
{
|
||||
return $this->bucket . '.cos.' . $this->region . '.myqcloud.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
protected function ssl()
|
||||
{
|
||||
return strstr($this->uploadUrl, 'https://') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查参数
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
protected function checkOptions()
|
||||
{
|
||||
if (!$this->bucket) {
|
||||
throw new UploadException('请传入桶名');
|
||||
}
|
||||
if (!$this->region) {
|
||||
throw new UploadException('请传入所属地域');
|
||||
}
|
||||
if (!$this->accessKey) {
|
||||
throw new UploadException('请传入SecretId');
|
||||
}
|
||||
if (!$this->secretKey) {
|
||||
throw new UploadException('请传入SecretKey');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param string $key
|
||||
* @param $body
|
||||
* @return string[]
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
public function putObject(string $key, $body)
|
||||
{
|
||||
|
||||
$this->checkOptions();
|
||||
|
||||
$url = $this->makeUpUrl();
|
||||
|
||||
$header = [
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'x-cos-acl' => $this->cosacl,
|
||||
'Content-MD5' => base64_encode(md5($body, true)),
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
$imageUrl = ($this->ssl() ? 'https://' : 'http://') . $url . '/' . $key;
|
||||
|
||||
$res = $this->request($imageUrl, 'PUT', ['body' => $body], $header);
|
||||
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $key,
|
||||
'path' => $imageUrl
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param string $bucket
|
||||
* @param string $key
|
||||
* @return array|false
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/19
|
||||
*/
|
||||
public function deleteObject(string $bucket, string $key)
|
||||
{
|
||||
$url = $this->getRequestHost($bucket);
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $url . '/' . $key, 'delete', [], $header);
|
||||
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶列表
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/19
|
||||
*/
|
||||
public function listBuckets()
|
||||
{
|
||||
$url = 'service.cos.myqcloud.com';
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $url . '/', 'get', [], $header);
|
||||
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测桶,不存在返回true
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return bool
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function headBucket(string $bucket, string $region = '')
|
||||
{
|
||||
$url = $this->getRequestHost($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
$this->request('https://' . $url, 'head', [], $header);
|
||||
|
||||
$response = $this->getResponse();
|
||||
|
||||
return $response['code'] == 404;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $acl
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function createBucket(string $bucket, string $region = '', string $acl = 'public-read')
|
||||
{
|
||||
return $this->noBodyRequest('put', $bucket, $region, $acl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合成xml
|
||||
* @param array $data
|
||||
* @param string $root
|
||||
* @param string $itemKey
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
protected function xmlBuild(array $xmlAttr, string $root = 'xml', string $itemKey = 'item')
|
||||
{
|
||||
$xml = '<' . $root . '>';
|
||||
$xml .= '<' . $itemKey . '>';
|
||||
|
||||
foreach ($xmlAttr as $kk => $vv) {
|
||||
if (is_array($vv)) {
|
||||
foreach ($vv as $v) {
|
||||
$xml .= '<' . $kk . '>' . $v . '</' . $kk . '>';
|
||||
}
|
||||
} else {
|
||||
$xml .= '<' . $kk . '>' . $vv . '</' . $kk . '>';
|
||||
}
|
||||
}
|
||||
$xml .= '</' . $itemKey . '>';
|
||||
$xml .= '</' . $root . '>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置跨域
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param array $data
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function putBucketCors(string $bucket, array $data, string $region = '')
|
||||
{
|
||||
$url = $this->getRequestHost($bucket, $region);
|
||||
|
||||
$xml = $this->xmlBuild($data, 'CORSConfiguration', 'CORSRule');
|
||||
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
'Content-Type' => 'application/xml',
|
||||
'Content-Length' => strlen($xml),
|
||||
'Content-MD5' => base64_encode(md5($xml, true))
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $url . '/?cors', 'put', ['xml' => $xml], $header);
|
||||
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function deleteBucket(string $name, string $region = '')
|
||||
{
|
||||
return $this->noBodyRequest('delete', $name, $region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶下的
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function getBucketDomain(string $name, string $region = '')
|
||||
{
|
||||
$this->action = 'domain';
|
||||
return $this->noBodyRequest('get', $name, $region);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定域名
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param array $data
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/19
|
||||
*/
|
||||
public function putBucketDomain(string $bucket, string $region, array $data)
|
||||
{
|
||||
$url = $this->getRequestHost($bucket, $region);
|
||||
|
||||
$xml = $this->xmlBuild($data, 'DomainConfiguration', 'DomainRule');
|
||||
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
'Content-Type' => 'application/xml',
|
||||
'Content-Length' => strlen($xml),
|
||||
'Content-MD5' => base64_encode(md5($xml, true))
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $url . '/?domain', 'put', ['xml' => $xml], $header);
|
||||
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
protected function getRequestHost(string $bucket, string $region = '')
|
||||
{
|
||||
if (!$this->accessKey) {
|
||||
throw new UploadException('请传入SecretId');
|
||||
}
|
||||
if (!$this->secretKey) {
|
||||
throw new UploadException('请传入SecretKey');
|
||||
}
|
||||
|
||||
if (strstr($bucket, '-') === false) {
|
||||
$bucket = $bucket . '-' . $this->appid;
|
||||
}
|
||||
|
||||
return $bucket . '.cos.' . ($region ?: $this->region) . '.myqcloud.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string|null $acl
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
public function noBodyRequest(string $method, string $bucket, string $region = '', string $acl = null, bool $isExc = true)
|
||||
{
|
||||
|
||||
$url = $this->getRequestHost($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
if ($acl) {
|
||||
$header['x-cos-acl'] = $acl;
|
||||
}
|
||||
|
||||
if (in_array($method, ['put', 'post'])) {
|
||||
$header['Content-Length'] = 0;
|
||||
}
|
||||
|
||||
$res = $this->request('https://' . $url . '/' . ($this->action ? '?' . $this->action : ''), $method, [], $header);
|
||||
$this->action = '';
|
||||
|
||||
if ($isExc) {
|
||||
if ($res && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $data
|
||||
* @param array $header
|
||||
* @param int $timeout
|
||||
* @return array|false|\SimpleXMLElement|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
public function request(string $url, string $method, array $data, array $header = [], int $timeout = 5)
|
||||
{
|
||||
|
||||
$this->request['body'] = $data;
|
||||
$this->request['host'] = $url;
|
||||
|
||||
|
||||
$urlAttr = parse_url($url);
|
||||
$curl = curl_init($url);
|
||||
$method = strtoupper($method);
|
||||
//请求方式
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
//超时时间
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
|
||||
//设置header头
|
||||
|
||||
$header = array_merge($header, $this->getSign($url, $method, $urlAttr['path'] ?? '', [], $header));
|
||||
|
||||
$this->request['header'] = $header;
|
||||
|
||||
$clientHeader = [];
|
||||
foreach ($header as $key => $item) {
|
||||
$clientHeader[] = $key . ':' . $item;
|
||||
}
|
||||
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, $clientHeader);
|
||||
|
||||
|
||||
curl_setopt($curl, CURLOPT_FAILONERROR, false);
|
||||
//返回抓取数据
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
//输出header头信息
|
||||
curl_setopt($curl, CURLOPT_HEADER, true);
|
||||
//TRUE 时追踪句柄的请求字符串,从 PHP 5.1.3 开始可用。这个很关键,就是允许你查看请求header
|
||||
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
|
||||
//https请求
|
||||
if (1 == strpos("$" . $url, "https://")) {
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
||||
}
|
||||
|
||||
//post请求
|
||||
if ($method == 'PUT' && !empty($data['body'])) {
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
// 注意这里的'file'是上传地址指定的key名
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data['body']);
|
||||
}
|
||||
|
||||
if (!empty($data['xml'])) {
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data['xml']);
|
||||
}
|
||||
|
||||
[$content, $status] = [curl_exec($curl), curl_getinfo($curl), curl_close($curl)];
|
||||
|
||||
$content = trim(substr($content, $status['header_size']));
|
||||
|
||||
$this->response['content'] = $content;
|
||||
$this->response['code'] = $status['http_code'];
|
||||
$this->response['header'] = $status;
|
||||
|
||||
$res = XML::parse($content);
|
||||
if ($res) {
|
||||
return $res;
|
||||
}
|
||||
return (intval($status["http_code"]) === 200) ? $content : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
* @param string $method
|
||||
* @param string $urlPath
|
||||
* @param array $query
|
||||
* @param array $headers
|
||||
* @return array
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/27
|
||||
*/
|
||||
public function getSign(string $url, string $method, string $urlPath, array $query = [], array $headers = [])
|
||||
{
|
||||
return (new Signature($this->accessKey, $this->secretKey, ['signHost' => $url]))->signRequest($method, $urlPath, $query, $headers);
|
||||
}
|
||||
}
|
||||
77
crmeb/services/upload/extend/cos/Scope.php
Normal file
77
crmeb/services/upload/extend/cos/Scope.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\cos;
|
||||
|
||||
|
||||
class Scope
|
||||
{
|
||||
protected $action;
|
||||
protected $bucket;
|
||||
protected $region;
|
||||
protected $resourcePrefix;
|
||||
protected $effect = 'allow';
|
||||
|
||||
public function __construct($action, $bucket, $region, $resourcePrefix)
|
||||
{
|
||||
$this->action = $action;
|
||||
$this->bucket = $bucket;
|
||||
$this->region = $region;
|
||||
$this->resourcePrefix = $resourcePrefix;
|
||||
}
|
||||
|
||||
public function set_effect($isAllow)
|
||||
{
|
||||
if ($isAllow) {
|
||||
$this->effect = 'allow';
|
||||
} else {
|
||||
$this->effect = 'deny';
|
||||
}
|
||||
}
|
||||
|
||||
public function get_action()
|
||||
{
|
||||
if ($this->action == null) {
|
||||
throw new \Exception("action == null");
|
||||
}
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function get_resource()
|
||||
{
|
||||
if ($this->bucket == null) {
|
||||
throw new \Exception("bucket == null");
|
||||
}
|
||||
if ($this->region == null) {
|
||||
throw new \Exception("region == null");
|
||||
}
|
||||
if ($this->resourcePrefix == null) {
|
||||
throw new \Exception("resourcePrefix == null");
|
||||
}
|
||||
$index = strripos($this->bucket, '-');
|
||||
if ($index < 0) {
|
||||
throw new Exception("bucket is invalid: " . $this->bucket);
|
||||
}
|
||||
$appid = substr($this->bucket, $index + 1);
|
||||
if (!(strpos($this->resourcePrefix, '/') === 0)) {
|
||||
$this->resourcePrefix = '/' . $this->resourcePrefix;
|
||||
}
|
||||
return 'qcs::cos:' . $this->region . ':uid/' . $appid . ':' . $this->bucket . $this->resourcePrefix;
|
||||
}
|
||||
|
||||
public function get_effect()
|
||||
{
|
||||
return $this->effect;
|
||||
}
|
||||
|
||||
}
|
||||
213
crmeb/services/upload/extend/cos/Signature.php
Normal file
213
crmeb/services/upload/extend/cos/Signature.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\cos;
|
||||
|
||||
/**
|
||||
* Class 生成签名
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/26
|
||||
* @package crmeb\services\upload\extend\cos
|
||||
*/
|
||||
class Signature
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $accessKey;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $secretKey;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Signature constructor.
|
||||
* @param string $accessKey
|
||||
* @param string $secretKey
|
||||
* @param array $options
|
||||
* @param string $token
|
||||
*/
|
||||
public function __construct(string $accessKey, string $secretKey, array $options = [], string $token = '')
|
||||
{
|
||||
$this->accessKey = $accessKey;
|
||||
$this->secretKey = $secretKey;
|
||||
$this->options = $options;
|
||||
$this->token = $token;
|
||||
$this->signHeader = [
|
||||
'cache-control',
|
||||
'content-disposition',
|
||||
'content-encoding',
|
||||
'content-length',
|
||||
'content-md5',
|
||||
'content-type',
|
||||
'expect',
|
||||
'expires',
|
||||
'host',
|
||||
'if-match',
|
||||
'if-modified-since',
|
||||
'if-none-match',
|
||||
'if-unmodified-since',
|
||||
'origin',
|
||||
'range',
|
||||
'response-cache-control',
|
||||
'response-content-disposition',
|
||||
'response-content-encoding',
|
||||
'response-content-language',
|
||||
'response-content-type',
|
||||
'response-expires',
|
||||
'transfer-encoding',
|
||||
'versionid',
|
||||
];
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
}
|
||||
|
||||
public function needCheckHeader($header)
|
||||
{
|
||||
if ($this->startWith($header, 'x-cos-')) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($header, $this->signHeader)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
* @param $haystack
|
||||
* @param $needle
|
||||
* @return bool
|
||||
*/
|
||||
protected function startWith($haystack, $needle)
|
||||
{
|
||||
$length = strlen($needle);
|
||||
if ($length == 0) {
|
||||
return true;
|
||||
}
|
||||
return (substr($haystack, 0, $length) === $needle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $urlPath
|
||||
* @param array $querys
|
||||
* @param array $headers
|
||||
* @return string[]
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/26
|
||||
*/
|
||||
public function signRequest(string $method, string $urlPath, array $querys = [], array $headers = [])
|
||||
{
|
||||
$authorization = $this->createAuthorization($method, $urlPath, $querys, $headers);
|
||||
return ['Authorization' => $authorization];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $urlPath
|
||||
* @param array $querys
|
||||
* @param array $headers
|
||||
* @param string $expires
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/26
|
||||
*/
|
||||
public function createAuthorization(string $method, string $urlPath, array $querys = [], array $headers = [], $expires = '+30 minutes')
|
||||
{
|
||||
if (is_null($expires) || !strtotime($expires)) {
|
||||
$expires = '+30 minutes';
|
||||
}
|
||||
$signTime = ( string )(time() - 60) . ';' . ( string )(strtotime($expires));
|
||||
$urlParamListArray = [];
|
||||
foreach ($querys as $query) {
|
||||
if (!empty($query)) {
|
||||
$tmpquery = explode('=', $query);
|
||||
//为了保证CI的key中有=号的情况也能正常通过,ci在这层之前已经encode了,这里需要拆开重新encode,防止上方explode拆错
|
||||
$key = strtolower(rawurlencode(urldecode($tmpquery[0])));
|
||||
if (count($tmpquery) >= 2) {
|
||||
$value = $tmpquery[1];
|
||||
} else {
|
||||
$value = "";
|
||||
}
|
||||
//host开关
|
||||
if (!$this->options['signHost'] && $key == 'host') {
|
||||
continue;
|
||||
}
|
||||
$urlParamListArray[$key] = $key . '=' . $value;
|
||||
}
|
||||
}
|
||||
ksort($urlParamListArray);
|
||||
$urlParamList = join(';', array_keys($urlParamListArray));
|
||||
$httpParameters = join('&', array_values($urlParamListArray));
|
||||
|
||||
$headerListArray = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
$key = strtolower(urlencode($key));
|
||||
$value = rawurlencode($value);
|
||||
if (!$this->options['signHost'] && $key == 'host') {
|
||||
continue;
|
||||
}
|
||||
if ($this->needCheckHeader($key)) {
|
||||
$headerListArray[$key] = $key . '=' . $value;
|
||||
}
|
||||
}
|
||||
ksort($headerListArray);
|
||||
$headerList = join(';', array_keys($headerListArray));
|
||||
$httpHeaders = join('&', array_values($headerListArray));
|
||||
$httpString = strtolower($method) . "\n" . urldecode($urlPath) . "\n" . $httpParameters .
|
||||
"\n" . $httpHeaders . "\n";
|
||||
|
||||
$sha1edHttpString = sha1($httpString);
|
||||
$stringToSign = "sha1\n$signTime\n$sha1edHttpString\n";
|
||||
$signKey = hash_hmac('sha1', $signTime, trim($this->secretKey));
|
||||
$signature = hash_hmac('sha1', $stringToSign, $signKey);
|
||||
$authorization = 'q-sign-algorithm=sha1&q-ak=' . trim($this->accessKey) .
|
||||
"&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=$headerList&q-url-param-list=$urlParamList&" .
|
||||
"q-signature=$signature";
|
||||
return $authorization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param string $urlPath
|
||||
* @param array $querys
|
||||
* @param array $headers
|
||||
* @param string $expires
|
||||
* @return string[]
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/26
|
||||
*/
|
||||
public function createPresignedUrl(string $url, string $method, string $urlPath, array $querys = [], array $headers = [], string $expires = '+30 minutes')
|
||||
{
|
||||
$authorization = $this->createAuthorization($method, $urlPath, $querys, $headers, $expires);
|
||||
$uri = $url;
|
||||
$query = 'sign=' . urlencode($authorization) . '&' . implode('&', $querys);
|
||||
if ($this->token != null) {
|
||||
$query = $query . '&x-cos-security-token=' . $this->token;
|
||||
}
|
||||
return [$uri, $query];
|
||||
}
|
||||
}
|
||||
321
crmeb/services/upload/extend/cos/Sts.php
Normal file
321
crmeb/services/upload/extend/cos/Sts.php
Normal file
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\cos;
|
||||
|
||||
|
||||
class Sts
|
||||
{
|
||||
// 临时密钥计算样例
|
||||
function _hex2bin($data)
|
||||
{
|
||||
$len = strlen($data);
|
||||
return pack("H" . $len, $data);
|
||||
}
|
||||
|
||||
// obj 转 query string
|
||||
function json2str($obj, $notEncode = false)
|
||||
{
|
||||
ksort($obj);
|
||||
$arr = array();
|
||||
if (!is_array($obj)) {
|
||||
throw new \Exception('$obj must be an array, the actual value is:' . json_encode($obj));
|
||||
}
|
||||
foreach ($obj as $key => $val) {
|
||||
array_push($arr, $key . '=' . ($notEncode ? $val : rawurlencode($val)));
|
||||
}
|
||||
return join('&', $arr);
|
||||
}
|
||||
|
||||
// 计算临时密钥用的签名
|
||||
function getSignature($opt, $key, $method, $config)
|
||||
{
|
||||
$host = "sts.tencentcloudapi.com";
|
||||
|
||||
if (array_key_exists('domain', $config)) {
|
||||
$host = $config['domain'];
|
||||
}
|
||||
|
||||
if (array_key_exists('endpoint', $config)) {
|
||||
$host = "sts." . $config['endpoint'];
|
||||
}
|
||||
|
||||
$formatString = $method . $host . '/?' . $this->json2str($opt, 1);
|
||||
$sign = hash_hmac('sha1', $formatString, $key);
|
||||
$sign = base64_encode($this->_hex2bin($sign));
|
||||
return $sign;
|
||||
}
|
||||
|
||||
// v2接口的key首字母小写,v3改成大写,此处做了向下兼容
|
||||
function backwardCompat($result)
|
||||
{
|
||||
if (!is_array($result)) {
|
||||
throw new \Exception('$result must be an array, the actual value is:' . json_encode($result));
|
||||
}
|
||||
$compat = array();
|
||||
foreach ($result as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$compat[lcfirst($key)] = $this->backwardCompat($value);
|
||||
} elseif ($key == 'Token') {
|
||||
$compat['sessionToken'] = $value;
|
||||
} else {
|
||||
$compat[lcfirst($key)] = $value;
|
||||
}
|
||||
}
|
||||
return $compat;
|
||||
}
|
||||
|
||||
// 获取临时密钥
|
||||
function getTempKeys($config)
|
||||
{
|
||||
$result = null;
|
||||
try {
|
||||
if (array_key_exists('policy', $config)) {
|
||||
$policy = $config['policy'];
|
||||
} else {
|
||||
if (array_key_exists('bucket', $config)) {
|
||||
$ShortBucketName = substr($config['bucket'], 0, strripos($config['bucket'], '-'));
|
||||
$AppId = substr($config['bucket'], 1 + strripos($config['bucket'], '-'));
|
||||
} else {
|
||||
throw new \Exception("bucket== null");
|
||||
}
|
||||
if (array_key_exists('allowPrefix', $config)) {
|
||||
if (!(strpos($config['allowPrefix'], '/') === 0)) {
|
||||
$config['allowPrefix'] = '/' . $config['allowPrefix'];
|
||||
}
|
||||
} else {
|
||||
throw new \Exception("allowPrefix == null");
|
||||
}
|
||||
if (!array_key_exists('region', $config)) {
|
||||
throw new \Exception("region == null");
|
||||
}
|
||||
$policy = array(
|
||||
'version' => '2.0',
|
||||
'statement' => array(
|
||||
array(
|
||||
'action' => $config['allowActions'],
|
||||
'effect' => 'allow',
|
||||
'resource' => array(
|
||||
'qcs::cos:' . $config['region'] . ':uid/' . $AppId . ':' . $config['bucket'] . $config['allowPrefix']
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$policyStr = str_replace('\\/', '/', json_encode($policy));
|
||||
$Action = 'GetFederationToken';
|
||||
$Nonce = rand(10000, 20000);
|
||||
$Timestamp = time();
|
||||
$Method = 'POST';
|
||||
if (array_key_exists('durationSeconds', $config)) {
|
||||
if (!(is_integer($config['durationSeconds']))) {
|
||||
throw new \Exception("durationSeconds must be a int type");
|
||||
}
|
||||
}
|
||||
$params = array(
|
||||
'SecretId' => $config['secretId'],
|
||||
'Timestamp' => $Timestamp,
|
||||
'Nonce' => $Nonce,
|
||||
'Action' => $Action,
|
||||
'DurationSeconds' => $config['durationSeconds'],
|
||||
'Version' => '2018-08-13',
|
||||
'Name' => 'cos',
|
||||
'Region' => $config['region'],
|
||||
'Policy' => urlencode($policyStr)
|
||||
);
|
||||
$params['Signature'] = $this->getSignature($params, $config['secretKey'], $Method, $config);
|
||||
$url = 'https://sts.tencentcloudapi.com/';
|
||||
|
||||
if (array_key_exists('url', $config)) {
|
||||
$url = $config['url'];
|
||||
}
|
||||
|
||||
if (!array_key_exists('url', $config) && array_key_exists('domain', $config)) {
|
||||
$url = 'https://sts.' . $config['domain'];
|
||||
}
|
||||
|
||||
if (array_key_exists('endpoint', $config)) {
|
||||
$url = 'https://sts.' . $config['endpoint'];
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
if (array_key_exists('proxy', $config)) {
|
||||
$config['proxy'] && curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json2str($params));
|
||||
$result = curl_exec($ch);
|
||||
if (curl_errno($ch)) $result = curl_error($ch);
|
||||
curl_close($ch);
|
||||
$result = json_decode($result, 1);
|
||||
if (isset($result['Response'])) {
|
||||
$result = $result['Response'];
|
||||
if (isset($result['Error'])) {
|
||||
throw new \Exception("get cam failed");
|
||||
}
|
||||
$result['startTime'] = $result['ExpiredTime'] - $config['durationSeconds'];
|
||||
}
|
||||
$result = $this->backwardCompat($result);
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
if ($result == null) {
|
||||
$result = "error: " . $e->getMessage();
|
||||
} else {
|
||||
$result = json_encode($result);
|
||||
}
|
||||
throw new \Exception($result);
|
||||
}
|
||||
}
|
||||
|
||||
//申请角色授权
|
||||
function getRoleCredential($config)
|
||||
{
|
||||
$result = null;
|
||||
try {
|
||||
if (array_key_exists('policy', $config)) {
|
||||
$policy = $config['policy'];
|
||||
} else {
|
||||
if (array_key_exists('bucket', $config)) {
|
||||
$ShortBucketName = substr($config['bucket'], 0, strripos($config['bucket'], '-'));
|
||||
$AppId = substr($config['bucket'], 1 + strripos($config['bucket'], '-'));
|
||||
} else {
|
||||
throw new \Exception("bucket== null");
|
||||
}
|
||||
if (array_key_exists('allowPrefix', $config)) {
|
||||
if (!(strpos($config['allowPrefix'], '/') === 0)) {
|
||||
$config['allowPrefix'] = '/' . $config['allowPrefix'];
|
||||
}
|
||||
} else {
|
||||
throw new \Exception("allowPrefix == null");
|
||||
}
|
||||
if (!array_key_exists('region', $config)) {
|
||||
throw new \Exception("region == null");
|
||||
}
|
||||
$policy = array(
|
||||
'version' => '2.0',
|
||||
'statement' => array(
|
||||
array(
|
||||
'action' => $config['allowActions'],
|
||||
'effect' => 'allow',
|
||||
'resource' => array(
|
||||
'qcs::cos:' . $config['region'] . ':uid/' . $AppId . ':' . $config['bucket'] . $config['allowPrefix']
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (array_key_exists('roleArn', $config)) {
|
||||
$RoleArn = $config['roleArn'];
|
||||
} else {
|
||||
throw new \Exception("roleArn == null");
|
||||
}
|
||||
$policyStr = str_replace('\\/', '/', json_encode($policy));
|
||||
$Action = 'AssumeRole';
|
||||
$Nonce = rand(10000, 20000);
|
||||
$Timestamp = time();
|
||||
$Method = 'POST';
|
||||
$ExternalId = "";
|
||||
if (array_key_exists('externalId', $config)) {
|
||||
$ExternalId = $config['externalId'];
|
||||
}
|
||||
if (array_key_exists('durationSeconds', $config)) {
|
||||
if (!(is_integer($config['durationSeconds']))) {
|
||||
throw new \Exception("durationSeconds must be a int type");
|
||||
}
|
||||
}
|
||||
$params = array(
|
||||
'SecretId' => $config['secretId'],
|
||||
'Timestamp' => $Timestamp,
|
||||
'RoleArn' => $RoleArn,
|
||||
'Action' => $Action,
|
||||
'Nonce' => $Nonce,
|
||||
'DurationSeconds' => $config['durationSeconds'],
|
||||
'Version' => '2018-08-13',
|
||||
'RoleSessionName' => 'cos',
|
||||
'Region' => $config['region'],
|
||||
'ExternalId' => $ExternalId,
|
||||
'Policy' => urlencode($policyStr)
|
||||
);
|
||||
$params['Signature'] = $this->getSignature($params, $config['secretKey'], $Method, $config);
|
||||
$url = 'https://sts.internal.tencentcloudapi.com/';
|
||||
|
||||
if (array_key_exists('endpoint', $config)) {
|
||||
$url = 'https://sts.' . $config['endpoint'];
|
||||
}
|
||||
$ch = curl_init($url);
|
||||
if (array_key_exists('proxy', $config)) {
|
||||
$config['proxy'] && curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json2str($params));
|
||||
$result = curl_exec($ch);
|
||||
if (curl_errno($ch)) $result = curl_error($ch);
|
||||
curl_close($ch);
|
||||
$result = json_decode($result, 1);
|
||||
if (isset($result['Response'])) {
|
||||
$result = $result['Response'];
|
||||
if (isset($result['Error'])) {
|
||||
throw new \Exception("get cam failed");
|
||||
}
|
||||
$result['startTime'] = $result['ExpiredTime'] - $config['durationSeconds'];
|
||||
}
|
||||
$result = $this->backwardCompat($result);
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
if ($result == null) {
|
||||
$result = "error: " . $e->getMessage();
|
||||
} else {
|
||||
$result = json_encode($result);
|
||||
}
|
||||
throw new \Exception($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get policy
|
||||
function getPolicy($scopes)
|
||||
{
|
||||
if (!is_array($scopes)) {
|
||||
return null;
|
||||
}
|
||||
$statements = [];
|
||||
|
||||
for ($i = 0, $counts = count($scopes); $i < $counts; $i++) {
|
||||
$actions = [];
|
||||
$resources = [];
|
||||
$actions[] = $scopes[$i]->get_action();
|
||||
$resources[] = $scopes[$i]->get_resource();
|
||||
|
||||
$statement = [
|
||||
'action' => $actions,
|
||||
'effect' => $scopes[$i]->get_effect(),
|
||||
'resource' => $resources
|
||||
];
|
||||
$statements[] = $statement;
|
||||
}
|
||||
|
||||
$policy = [
|
||||
'version' => '2.0',
|
||||
'statement' => $statements
|
||||
];
|
||||
return $policy;
|
||||
}
|
||||
}
|
||||
444
crmeb/services/upload/extend/jdoss/Client.php
Normal file
444
crmeb/services/upload/extend/jdoss/Client.php
Normal file
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\jdoss;
|
||||
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\upload\BaseClient;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
|
||||
/**
|
||||
* 京东云上传
|
||||
* Class Client
|
||||
* @package crmeb\services\upload\extend\jdoss
|
||||
*/
|
||||
class Client extends BaseClient
|
||||
{
|
||||
|
||||
const ALGORITHM_REQUEST = 'AWS4-HMAC-SHA256';
|
||||
|
||||
const BLACKLIST_HEADERS = [
|
||||
'cache-control' => true,
|
||||
'content-type' => true,
|
||||
'content-length' => true,
|
||||
'expect' => true,
|
||||
'max-forwards' => true,
|
||||
'pragma' => true,
|
||||
'range' => true,
|
||||
'te' => true,
|
||||
'if-match' => true,
|
||||
'if-none-match' => true,
|
||||
'if-modified-since' => true,
|
||||
'if-unmodified-since' => true,
|
||||
'if-range' => true,
|
||||
'accept' => true,
|
||||
'authorization' => true,
|
||||
'proxy-authorization' => true,
|
||||
'from' => true,
|
||||
'referer' => true,
|
||||
'user-agent' => true,
|
||||
'x-amzn-trace-id' => true,
|
||||
'aws-sdk-invocation-id' => true,
|
||||
'aws-sdk-retry' => true,
|
||||
];
|
||||
/**
|
||||
* AK
|
||||
* @var
|
||||
*/
|
||||
protected $accessKeyId;
|
||||
|
||||
/**
|
||||
* SK
|
||||
* @var
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 桶名
|
||||
* @var string
|
||||
*/
|
||||
protected $bucketName;
|
||||
|
||||
/**
|
||||
* 地区
|
||||
* @var string
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl = 's3.<REGION>.jdcloud-oss.com';
|
||||
|
||||
//默认地域
|
||||
const DEFAULT_REGION = 'cn-north-1';
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->accessKeyId = $config['accessKey'] ?? '';
|
||||
$this->secretKey = $config['secretKey'] ?? '';
|
||||
$this->bucketName = $config['bucket'] ?? '';
|
||||
$this->region = $config['region'] ?? self::DEFAULT_REGION;
|
||||
$this->uploadUrl = $config['uploadUrl'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
* @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function putObject(string $bucket, string $region, string $key, array $data)
|
||||
{
|
||||
$url = $this->getRequestUrl($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
];
|
||||
if (isset($data['body'])) {
|
||||
$header['Content-Length'] = strlen($data['body']);
|
||||
}
|
||||
|
||||
return $this->request('https://' . $url . '/'. $key, 'PUT', $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $key
|
||||
* @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function deleteObject(string $bucket, string $region, string $key)
|
||||
{
|
||||
$url = $this->getRequestUrl($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
];
|
||||
|
||||
return $this->request('https://' . $url . '/'. $key, 'DELETE', [], $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶列表
|
||||
* @return array|bool|\crmeb\services\upload\SimpleXMLElement|mixed
|
||||
*/
|
||||
public function listBuckets()
|
||||
{
|
||||
$url = $this->getRequestUrl();
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $url . '/', 'GET', [], $header);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测桶,不存在返回true
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return array|bool|\crmeb\services\upload\SimpleXMLElement|mixed
|
||||
*/
|
||||
public function headBucket(string $bucket, string $region = '')
|
||||
{
|
||||
$url = $this->getRequestUrl($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
return $this->request('https://' . $url, 'head', [], $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param $name
|
||||
* @param $region
|
||||
* @param $acl
|
||||
* @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function createBucket($name, $region, $acl)
|
||||
{
|
||||
$url = $this->getRequestUrl($name, $region);
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
'x-amz-acl' => $acl
|
||||
];
|
||||
$res = $this->request('https://' . $url . '/', 'PUT', [], $header);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除桶
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function deleteBucket(string $bucket, string $region = '')
|
||||
{
|
||||
$url = $this->getRequestUrl($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url
|
||||
];
|
||||
|
||||
return $this->request('https://' . $url . '/', 'DELETE', [], $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置桶跨域规则
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param $data
|
||||
* @return array|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function putBucketCors(string $bucket, string $region = '', $data = [])
|
||||
{
|
||||
$url = $this->getRequestUrl($bucket, $region);
|
||||
|
||||
$header = [
|
||||
'Host' => $url,
|
||||
'content-type' => 'application/xml'
|
||||
];
|
||||
|
||||
return $this->request('https://' . $url . '/?cors', 'PUT', $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取host
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUrl(string $bucket = '', string $region = self::DEFAULT_REGION)
|
||||
{
|
||||
if (!$this->accessKeyId) {
|
||||
throw new UploadException('请传入SecretId');
|
||||
}
|
||||
if (!$this->secretKey) {
|
||||
throw new UploadException('请传入SecretKey');
|
||||
}
|
||||
|
||||
return ($bucket ? $bucket . '.' : '') . 's3.' . $region . '.jdcloud-oss.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $data
|
||||
* @param array $clientHeader
|
||||
* @param int $timeout
|
||||
* @return array|bool|\crmeb\services\upload\SimpleXMLElement|mixed
|
||||
*/
|
||||
protected function request(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10)
|
||||
{
|
||||
if (!isset($clientHeader['Content-Length'])) {
|
||||
$clientHeader['Content-Length'] = 0;
|
||||
}
|
||||
$clientHeader['x-amz-date'] = gmdate('Ymd\THis\Z');
|
||||
$clientHeader['x-amz-content-sha256'] = hash('sha256', $data['body'] ?? '', false);
|
||||
|
||||
$authorization = $this->generateAwsSignatureV4($data['region'] ?? self::DEFAULT_REGION, $url, $method, $clientHeader, $data);
|
||||
$clientHeader['Authorization'] = $authorization;
|
||||
|
||||
return $this->requestClient($url, $method, $data, $clientHeader, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* @param string $region
|
||||
* @param string $url
|
||||
* @param string $httpMethod
|
||||
* @param array $header
|
||||
* @param array $data
|
||||
* @param string $service
|
||||
* @return string
|
||||
*/
|
||||
protected function generateAwsSignatureV4(string $region, string $url, string $httpMethod, array $header, array $data = [], string $service = 's3')
|
||||
{
|
||||
$algorithm = self::ALGORITHM_REQUEST;
|
||||
$t = new \DateTime('UTC');
|
||||
$amzDate = $t->format('Ymd\THis\Z');
|
||||
$dateStamp = $t->format('Ymd');
|
||||
[$canonicalRequest, $signedHeaders] = $this->createCanonicalRequest($url, $httpMethod, $header, $data);
|
||||
|
||||
$credentialScope = $dateStamp . '/' . $region . '/' . $service . '/aws4_request';
|
||||
$stringToSign = $algorithm . "\n" . $amzDate . "\n" . $credentialScope . "\n" . hash('sha256', $canonicalRequest);
|
||||
$signingKey = hash_hmac('sha256', 'aws4_request',
|
||||
hash_hmac('sha256', $service,
|
||||
hash_hmac('sha256', $region,
|
||||
hash_hmac('sha256', $dateStamp, 'AWS4' . $this->secretKey, true),
|
||||
true),
|
||||
true),
|
||||
true);
|
||||
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
|
||||
|
||||
return $algorithm . ' Credential=' . $this->accessKeyId . '/' . $credentialScope . ', SignedHeaders=' . $signedHeaders . ', Signature=' . $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $httpMethod
|
||||
* @param array $header
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function createCanonicalRequest(string $url, string $httpMethod, array $header, array $data = [])
|
||||
{
|
||||
$canonicalQueryString = '';
|
||||
$payload = '';
|
||||
if (!empty($data['query'])) {
|
||||
$query = $payload = $data['query'];
|
||||
ksort($query);
|
||||
$queryAttr = [];
|
||||
foreach ($query as $key => $item) {
|
||||
$queryAttr[urlencode($key)] = urlencode($item);
|
||||
}
|
||||
if ($queryAttr) {
|
||||
$canonicalQueryString = implode('&', $queryAttr);
|
||||
}
|
||||
$payload = json_encode($payload);
|
||||
} elseif (!empty($data['body'])) {
|
||||
$payload = $data['body'];
|
||||
} elseif (!empty($data['json'])) {
|
||||
$payload = json_encode($data['json']);
|
||||
}
|
||||
$canonicalHeaders = '';
|
||||
$signedHeaders = '';
|
||||
if ($header) {
|
||||
$canonicalHeadersAtrr = $signedHeadersAttr = [];
|
||||
ksort($header);
|
||||
foreach ($header as $key => $item) {
|
||||
$key = strtolower($key);
|
||||
if (isset(self::BLACKLIST_HEADERS[$key])) {
|
||||
continue;
|
||||
}
|
||||
$canonicalHeadersAtrr[] = $key . ':' . preg_replace('/\s+/', ' ', trim($item));
|
||||
$signedHeadersAttr[] = strtolower($key);
|
||||
}
|
||||
ksort($canonicalHeadersAtrr);
|
||||
ksort($signedHeadersAttr);
|
||||
if ($canonicalHeadersAtrr) {
|
||||
$canonicalHeaders = implode("\n", $canonicalHeadersAtrr);
|
||||
$signedHeaders = implode(';', $signedHeadersAttr);
|
||||
}
|
||||
}
|
||||
$canonicalUri = $this->createCanonicalizedPath($url);
|
||||
$bodyDigest = $this->buildBodyDigest($header, (string)Utils::streamFor($payload));
|
||||
$canonicalRequest = $httpMethod . "\n" . $canonicalUri . "\n" . $canonicalQueryString . "\n" . $canonicalHeaders . "\n" . "\n" . $signedHeaders . "\n" . $bodyDigest;
|
||||
return [$canonicalRequest, $signedHeaders];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public function createCanonicalizedPath(string $url)
|
||||
{
|
||||
$canonicalUri = '/';
|
||||
|
||||
$urlAttr = pathinfo($url);
|
||||
if (isset($urlAttr['dirname']) && $urlAttr['dirname'] !== 'https:') {
|
||||
$urlParse = parse_url($urlAttr['dirname'] ?? '');
|
||||
if (isset($urlParse['path'])) {
|
||||
$canonicalUri .= substr($urlParse['path'], 1) . '/';
|
||||
}
|
||||
if (isset($urlAttr['basename'])) {
|
||||
$canonicalUri .= $urlAttr['basename'];
|
||||
}
|
||||
//|| strlen($canonicalUri) - 1 !== $pos
|
||||
if (!($pos = strripos($canonicalUri, '/')) ) {
|
||||
$canonicalUri .= '/';
|
||||
}
|
||||
}
|
||||
return $canonicalUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理dody hash
|
||||
* @param array $header
|
||||
* @param string $body
|
||||
* @return string
|
||||
*/
|
||||
protected function buildBodyDigest(array $header, string $body = ''): string
|
||||
{
|
||||
if (isset($header['x-amz-content-sha256'])) {
|
||||
$hash = $header['x-amz-content-sha256'];
|
||||
} else {
|
||||
$hash = hash('sha256', $body);
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理bogy
|
||||
* @param string $body
|
||||
* @return string
|
||||
*/
|
||||
public function dechunk(string $body): string
|
||||
{
|
||||
$h = fopen('php://temp', 'w+');
|
||||
stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE);
|
||||
fwrite($h, $body);
|
||||
$body = stream_get_contents($h, -1, 0);
|
||||
rewind($h);
|
||||
ftruncate($h, 0);
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域
|
||||
* @return \string[][]
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'cn-north-1',
|
||||
'label' => '华北-北京',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-south-1',
|
||||
'label' => '华南-广州',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-2',
|
||||
'label' => '华东-上海',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-1',
|
||||
'label' => '华东-宿迁',
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
717
crmeb/services/upload/extend/obs/Client.php
Normal file
717
crmeb/services/upload/extend/obs/Client.php
Normal file
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: CRMEB Team <admin@crmeb.com>
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace crmeb\services\upload\extend\obs;
|
||||
|
||||
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\upload\BaseClient;
|
||||
|
||||
/**
|
||||
* 华为云上传
|
||||
* Class Client
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
* @package crmeb\services\upload\extend\obs
|
||||
*/
|
||||
class Client extends BaseClient
|
||||
{
|
||||
const HEADER_PREFIX = 'x-obs-';
|
||||
|
||||
const INTEREST_HEADER_KEY_LIST = ['content-type', 'content-md5', 'date'];
|
||||
|
||||
const ALTERNATIVE_DATE_HEADER = 'x-obs-date';
|
||||
|
||||
const ALLOWED_RESOURCE_PARAMTER_NAMES = [
|
||||
'acl',
|
||||
'policy',
|
||||
'torrent',
|
||||
'logging',
|
||||
'location',
|
||||
'storageinfo',
|
||||
'quota',
|
||||
'storagepolicy',
|
||||
'requestpayment',
|
||||
'versions',
|
||||
'versioning',
|
||||
'versionid',
|
||||
'uploads',
|
||||
'uploadid',
|
||||
'partnumber',
|
||||
'website',
|
||||
'notification',
|
||||
'lifecycle',
|
||||
'deletebucket',
|
||||
'delete',
|
||||
'cors',
|
||||
'restore',
|
||||
'tagging',
|
||||
'response-content-type',
|
||||
'response-content-language',
|
||||
'response-expires',
|
||||
'response-cache-control',
|
||||
'response-content-disposition',
|
||||
'response-content-encoding',
|
||||
'x-image-process',
|
||||
|
||||
'backtosource',
|
||||
'storageclass',
|
||||
'replication',
|
||||
'append',
|
||||
'position',
|
||||
'x-oss-process'
|
||||
];
|
||||
|
||||
//桶acl
|
||||
const OBS_ACL = [
|
||||
[
|
||||
'value' => 'public-read',
|
||||
'label' => '公共读(推荐)',
|
||||
],
|
||||
[
|
||||
'value' => 'public-read-write',
|
||||
'label' => '公共读写',
|
||||
],
|
||||
];
|
||||
//默认acl
|
||||
const DEFAULT_OBS_ACL = 'public-read';
|
||||
|
||||
protected $isCname = false;
|
||||
|
||||
protected $pathStyle;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $accessKeyId;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 桶名
|
||||
* @var string
|
||||
*/
|
||||
protected $bucketName;
|
||||
|
||||
/**
|
||||
* 地区
|
||||
* @var string
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var mixed|string
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $baseUrl = 'obs.cn-north-1.myhuaweicloud.com';
|
||||
|
||||
protected $type = 'hw';
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->accessKeyId = $config['accessKey'] ?? '';
|
||||
$this->secretKey = $config['secretKey'] ?? '';
|
||||
$this->bucketName = $config['bucket'] ?? '';
|
||||
$this->region = $config['region'] ?? 'ap-chengdu';
|
||||
$this->uploadUrl = $config['uploadUrl'] ?? '';
|
||||
$this->type = $config['type'] ?? 'hw';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
protected function checkOptions()
|
||||
{
|
||||
if (!$this->bucketName) {
|
||||
throw new UploadException('请传入桶名');
|
||||
}
|
||||
if (!$this->region) {
|
||||
throw new UploadException('请传入所属地域');
|
||||
}
|
||||
if (!$this->accessKeyId) {
|
||||
throw new UploadException('请传入SecretId');
|
||||
}
|
||||
if (!$this->secretKey) {
|
||||
throw new UploadException('请传入SecretKey');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param string $key
|
||||
* @param $body
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function putObject(string $key, $body, string $contentType = 'image/jpeg')
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($this->bucketName, $this->region),
|
||||
'Content-Type' => $contentType,
|
||||
'Content-Length' => strlen($body),
|
||||
];
|
||||
|
||||
$res = $this->checkOptions()->request('https://' . $header['Host'] . '/' . $key, 'PUT', [
|
||||
'bucket' => $this->bucketName,
|
||||
'body' => $body
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除上传对象
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function deleteObject(string $key)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($this->bucketName, $this->region),
|
||||
];
|
||||
|
||||
$res = $this->request('https://' . $header['Host'] . '/' . $key, 'DELETE', [
|
||||
'bucket' => $this->bucketName
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶
|
||||
* @return false|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/16
|
||||
*/
|
||||
public function listBuckets()
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl('', $this->region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/', 'GET', [], []);
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
public function headBucket(string $bucket, string $region)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/', 'HEAD', [], []);
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置桶的策略
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*
|
||||
* @date 2023/06/08
|
||||
* @author yyw
|
||||
*/
|
||||
public function putPolicy(string $bucket, string $region, array $data)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
"Content-Type" => "application/json"
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/?policy', 'PUT', [
|
||||
'bucket' => $bucket,
|
||||
'json' => $data
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $acl
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function createBucket(string $bucket, string $region, string $acl = self::DEFAULT_OBS_ACL)
|
||||
{
|
||||
$header = [
|
||||
'x-obs-acl' => $acl,
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
"Content-Type" => "application/xml"
|
||||
];
|
||||
$xml = "<CreateBucketConfiguration><Location>{$region}</Location></CreateBucketConfiguration>";
|
||||
$res = $this->request('https://' . $header['Host'] . '/', 'PUT', [
|
||||
'bucket' => $bucket,
|
||||
'body' => $xml
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除桶
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function deleteBucket(string $bucket, string $region)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/', 'DELETE', [
|
||||
'bucket' => $bucket
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取桶的自定义域名
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function getBucketDomain(string $bucket, string $region)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/?customdomain', 'GET', [
|
||||
'bucket' => $bucket
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置桶的自定义域名
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function putBucketDomain(string $bucket, string $region, array $data = [])
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/?customdomain=' . $data['domainname'], 'PUT', [
|
||||
'bucket' => $bucket
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置跨域
|
||||
* @return bool
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
public function putBucketCors(string $bucket, string $region, array $data = [])
|
||||
{
|
||||
$xml = $this->xmlBuild($data, 'CORSConfiguration', 'CORSRule');
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
'Content-Type' => 'application/xml',
|
||||
'Content-Length' => strlen($xml),
|
||||
'Content-MD5' => base64_encode(md5($xml, true))
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/?cors', 'PUT', [
|
||||
'bucket' => $bucket,
|
||||
'body' => $xml
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除跨域
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return mixed
|
||||
*
|
||||
* @date 2023/06/08
|
||||
* @author yyw
|
||||
*/
|
||||
public function deleteBucketCors(string $bucket, string $region)
|
||||
{
|
||||
$header = [
|
||||
'Host' => $this->getRequestUrl($bucket, $region),
|
||||
];
|
||||
$res = $this->request('https://' . $header['Host'] . '/?cors', 'DELETE', [
|
||||
'bucket' => $bucket,
|
||||
], $header);
|
||||
|
||||
return $this->response($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $res
|
||||
* @return mixed
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/18
|
||||
*/
|
||||
protected function response($res)
|
||||
{
|
||||
if (!empty($res['Code']) && !empty($res['Message'])) {
|
||||
throw new UploadException($res['Message']);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求域名
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @return string
|
||||
*
|
||||
* @date 2023/06/08
|
||||
* @author yyw
|
||||
*/
|
||||
protected function getRequestUrl(string $bucket = '', string $region = '')
|
||||
{
|
||||
if ($this->type == 'hw') {
|
||||
$url = '.myhuaweicloud.com'; // 华为
|
||||
} else {
|
||||
$url = '.ctyun.cn'; // 天翼
|
||||
}
|
||||
if ($bucket) {
|
||||
return $bucket . '.obs.' . $region . $url;
|
||||
} else {
|
||||
return 'obs.' . $region . $url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 地域名称
|
||||
* @return \string[][]
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/17
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'cn-north-1',
|
||||
'label' => '华北-北京一',
|
||||
],
|
||||
// [
|
||||
// 'value' => 'cn-north-4',
|
||||
// 'label' => '华北-北京四',
|
||||
// ],
|
||||
[
|
||||
'value' => 'cn-north-9',
|
||||
'label' => '华北-乌兰察布一',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-2',
|
||||
'label' => '华东-上海二',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-3',
|
||||
'label' => '华东-上海一',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-south-1',
|
||||
'label' => '华南-广州',
|
||||
],
|
||||
[
|
||||
'value' => 'ap-southeast-1',
|
||||
'label' => '中国-香港',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-south-4',
|
||||
'label' => '华南-广州-友好用户环境',
|
||||
],
|
||||
[
|
||||
'value' => 'cn-southwest-2',
|
||||
'label' => '西南-贵阳一',
|
||||
],
|
||||
[
|
||||
'value' => 'la-north-2',
|
||||
'label' => '拉美-墨西哥城二',
|
||||
],
|
||||
[
|
||||
'value' => 'na-mexico-1',
|
||||
'label' => '拉美-墨西哥城一',
|
||||
],
|
||||
[
|
||||
'value' => 'sa-brazil-1',
|
||||
'label' => '拉美-圣保罗一',
|
||||
],
|
||||
[
|
||||
'value' => 'la-south-2',
|
||||
'label' => '拉美-圣地亚哥',
|
||||
],
|
||||
[
|
||||
'value' => 'tr-west-1',
|
||||
'label' => '土耳其-伊斯坦布尔',
|
||||
],
|
||||
[
|
||||
'value' => 'ap-southeast-2',
|
||||
'label' => '亚太-曼谷',
|
||||
],
|
||||
[
|
||||
'value' => 'ap-southeast-3',
|
||||
'label' => '亚太-新加坡',
|
||||
],
|
||||
[
|
||||
'value' => 'af-south-1',
|
||||
'label' => '非洲-约翰内斯堡',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置桶名
|
||||
* @param string $bucketName
|
||||
* @return $this
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/16
|
||||
*/
|
||||
public function setBucketName(string $bucketName)
|
||||
{
|
||||
$this->bucketName = $bucketName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
* @param array $result
|
||||
* @return array
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/17
|
||||
*/
|
||||
protected function getSign(array $result)
|
||||
{
|
||||
$result['headers']['Date'] = gmdate('D, d M Y H:i:s \G\M\T');
|
||||
$canonicalstring = $this->makeCanonicalstring($result['method'], $result['headers'], $result['pathArgs'], $result['dnsParam'], $result['uriParam']);
|
||||
|
||||
$result['cannonicalRequest'] = $canonicalstring;
|
||||
|
||||
$signature = base64_encode(hash_hmac('sha1', $canonicalstring, $this->secretKey, true));
|
||||
|
||||
$authorization = 'OBS ' . $this->accessKeyId . ':' . $signature;
|
||||
|
||||
$result['headers']['Authorization'] = $authorization;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理签名数据
|
||||
* @param $method
|
||||
* @param $headers
|
||||
* @param $pathArgs
|
||||
* @param $bucketName
|
||||
* @param $objectKey
|
||||
* @param null $expires
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/17
|
||||
*/
|
||||
public function makeCanonicalstring($method, $headers, $pathArgs, $bucketName, $objectKey, $expires = null)
|
||||
{
|
||||
$buffer = [];
|
||||
$buffer[] = $method;
|
||||
$buffer[] = "\n";
|
||||
$interestHeaders = [];
|
||||
|
||||
foreach ($headers as $key => $value) {
|
||||
$key = strtolower($key);
|
||||
if (in_array($key, self::INTEREST_HEADER_KEY_LIST) || strpos($key, self::HEADER_PREFIX) === 0) {
|
||||
$interestHeaders[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists(self::ALTERNATIVE_DATE_HEADER, $interestHeaders)) {
|
||||
$interestHeaders['date'] = '';
|
||||
}
|
||||
|
||||
if ($expires !== null) {
|
||||
$interestHeaders['date'] = strval($expires);
|
||||
}
|
||||
|
||||
if (!array_key_exists('content-type', $interestHeaders)) {
|
||||
$interestHeaders['content-type'] = '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('content-md5', $interestHeaders)) {
|
||||
$interestHeaders['content-md5'] = '';
|
||||
}
|
||||
|
||||
ksort($interestHeaders);
|
||||
|
||||
foreach ($interestHeaders as $key => $value) {
|
||||
if (strpos($key, self::HEADER_PREFIX) === 0) {
|
||||
$buffer[] = $key . ':' . $value;
|
||||
} else {
|
||||
$buffer[] = $value;
|
||||
}
|
||||
$buffer[] = "\n";
|
||||
}
|
||||
|
||||
$uri = '';
|
||||
|
||||
$bucketName = $this->isCname ? $headers['Host'] : $bucketName;
|
||||
|
||||
if ($bucketName) {
|
||||
$uri .= '/';
|
||||
$uri .= $bucketName;
|
||||
if (!$this->pathStyle) {
|
||||
$uri .= '/';
|
||||
}
|
||||
}
|
||||
|
||||
if ($objectKey) {
|
||||
if (!($pos = strripos($uri, '/')) || strlen($uri) - 1 !== $pos) {
|
||||
$uri .= '/';
|
||||
}
|
||||
$uri .= $objectKey;
|
||||
}
|
||||
|
||||
$buffer[] = $uri === '' ? '/' : $uri;
|
||||
|
||||
|
||||
if (!empty($pathArgs)) {
|
||||
ksort($pathArgs);
|
||||
$_pathArgs = [];
|
||||
foreach ($pathArgs as $key => $value) {
|
||||
if (in_array(strtolower($key), self::ALLOWED_RESOURCE_PARAMTER_NAMES) || strpos($key, self::HEADER_PREFIX) === 0) {
|
||||
$_pathArgs[] = $value === null || $value === '' ? $key : $key . '=' . urldecode($value);
|
||||
}
|
||||
}
|
||||
if (!empty($_pathArgs)) {
|
||||
$buffer[] = '?';
|
||||
$buffer[] = implode('&', $_pathArgs);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $data
|
||||
* @param array $clientHeader
|
||||
* @param int $timeout
|
||||
* @return false|string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2023/5/16
|
||||
*/
|
||||
public function request(string $url, string $method, array $data = [], array $clientHeader = [], int $timeout = 10)
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
$urlAttr = pathinfo($url);
|
||||
$urlParse = parse_url($urlAttr['dirname'] ?? '');
|
||||
|
||||
$uriParam = '';
|
||||
if ($urlAttr['dirname'] !== 'https:') {
|
||||
if (isset($urlParse['path'])) {
|
||||
$uriParam .= substr($urlParse['path'], 1) . '/';
|
||||
}
|
||||
if (isset($urlAttr['basename'])) {
|
||||
$uriParam .= $urlAttr['basename'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->getSign([
|
||||
'method' => $method,
|
||||
'headers' => $clientHeader,
|
||||
'pathArgs' => '',
|
||||
'dnsParam' => $data['bucket'] ?? '',
|
||||
'uriParam' => $uriParam,
|
||||
]);
|
||||
|
||||
return $this->requestClient($url, $method, $data, $result['headers'], $timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 组合成xml
|
||||
* @param array $data
|
||||
* @param string $root
|
||||
* @param string $itemKey
|
||||
* @return string
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/10/17
|
||||
*/
|
||||
protected function xmlBuild(array $xmlAttr, string $root = 'xml', string $itemKey = 'item')
|
||||
{
|
||||
$xml = '<' . $root . '>';
|
||||
$xml .= '<' . $itemKey . '>';
|
||||
|
||||
foreach ($xmlAttr as $kk => $vv) {
|
||||
if (is_array($vv)) {
|
||||
foreach ($vv as $v) {
|
||||
$xml .= '<' . $kk . '>' . $v . '</' . $kk . '>';
|
||||
}
|
||||
} else {
|
||||
$xml .= '<' . $kk . '>' . $vv . '</' . $kk . '>';
|
||||
}
|
||||
}
|
||||
$xml .= '</' . $itemKey . '>';
|
||||
$xml .= '</' . $root . '>';
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
}
|
||||
676
crmeb/services/upload/storage/Cos.php
Normal file
676
crmeb/services/upload/storage/Cos.php
Normal file
@@ -0,0 +1,676 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\basic\BaseUpload;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use think\exception\ValidateException;
|
||||
use QCloud\COSSTS\Sts;
|
||||
use crmeb\services\upload\extend\cos\Client as CrmebClient;
|
||||
|
||||
/**
|
||||
* 腾讯云COS文件上传
|
||||
* Class COS
|
||||
* @package crmeb\services\upload\storage
|
||||
*/
|
||||
class Cos extends BaseUpload
|
||||
{
|
||||
|
||||
/**
|
||||
* 应用id
|
||||
* @var string
|
||||
*/
|
||||
protected $appid;
|
||||
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var CrmebClient
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* 水印位置
|
||||
* @var string[]
|
||||
*/
|
||||
protected $position = [
|
||||
'1' => 'northwest',//:左上
|
||||
'2' => 'north',//:中上
|
||||
'3' => 'northeast',//:右上
|
||||
'4' => 'west',//:左中
|
||||
'5' => 'center',//:中部
|
||||
'6' => 'east',//:右中
|
||||
'7' => 'southwest',//:左下
|
||||
'8' => 'south',//:中下
|
||||
'9' => 'southeast',//:右下
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->appid = $config['appid'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
$this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return CrmebClient
|
||||
* @author 等风来
|
||||
* @email 136327134@qq.com
|
||||
* @date 2022/9/29
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
$this->handle = new CrmebClient([
|
||||
'accessKey' => $this->accessKey,
|
||||
'secretKey' => $this->secretKey,
|
||||
'region' => $this->storageRegion ?: 'ap-chengdu',
|
||||
'bucket' => $this->storageName,
|
||||
'appid' => $this->appid,
|
||||
'uploadUrl' => $this->uploadUrl
|
||||
]);
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param string|null $file
|
||||
* @param bool $isStream 是否为流上传
|
||||
* @param string|null $fileContent 流内容
|
||||
* @return array|bool|\StdClass
|
||||
*/
|
||||
protected function upload(string $file = null, bool $isStream = false, string $fileContent = null)
|
||||
{
|
||||
if (!$isStream) {
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('Upload file does not exist');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
$body = fopen($fileHandle->getRealPath(), 'rb');
|
||||
$body = (string)Utils::streamFor($body);
|
||||
} else {
|
||||
$key = $file;
|
||||
$body = $fileContent;
|
||||
}
|
||||
try {
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
$this->verifyExtension($key);
|
||||
|
||||
$this->fileInfo->uploadInfo = $this->app()->putObject($key, $body);
|
||||
$this->fileInfo->filePath = $this->uploadUrl . '/' . $key;
|
||||
$this->fileInfo->realName = isset($fileHandle) ? $fileHandle->getOriginalName() : $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
return $this->upload($key, true, $fileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* @param string $file
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function move(string $file = 'file')
|
||||
{
|
||||
return $this->upload($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @return mixed|string[]
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
if (isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?imageMogr2/thumbnail/' . $config[$width] . 'x' . $config[$height];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?') === false) {
|
||||
$filePath .= '?watermark';
|
||||
} else {
|
||||
$filePath .= '&watermark';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new ValidateException('请先配置水印图片');
|
||||
}
|
||||
$waterPath = $filePath .= '/1/image/' . base64_encode($waterConfig['watermark_image']) . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'northwest') . '/blogo/1/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new ValidateException('请先配置水印文字');
|
||||
}
|
||||
$waterPath = $filePath .= '/2/text/' . base64_encode($waterConfig['watermark_text']) . '/font/' . base64_encode($waterConfig['watermark_text_font']) . '/fill/' . base64_encode($waterConfig['watermark_text_color']) . '/fontsize/' . $waterConfig['watermark_text_size'] . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'northwest') . '/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频封面图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @param int $time
|
||||
* @return array
|
||||
*/
|
||||
public function videoCoverImage(string $filePath = '', string $type = 'all', int $time = 1)
|
||||
{
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
//?ci-process=snapshot
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 600;
|
||||
$width = 400;
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?ci-process=snapshot&time=' . $time . '&format=jpg&width=' . $width . '&height=' . $height ;
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除资源
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(string $filePath)
|
||||
{
|
||||
try {
|
||||
return $this->app()->deleteObject($this->storageName, $filePath);
|
||||
} catch (\Exception $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
* @return array|mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getTempKeys()
|
||||
{
|
||||
$sts = new Sts();
|
||||
$config = [
|
||||
'url' => 'https://sts.tencentcloudapi.com/',
|
||||
'domain' => 'sts.tencentcloudapi.com',
|
||||
'proxy' => '',
|
||||
'secretId' => $this->accessKey, // 固定密钥
|
||||
'secretKey' => $this->secretKey, // 固定密钥
|
||||
'bucket' => $this->storageName, // 换成你的 bucket
|
||||
'region' => $this->storageRegion, // 换成 bucket 所在园区
|
||||
'durationSeconds' => 1800, // 密钥有效期
|
||||
'allowPrefix' => '*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
|
||||
// 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
|
||||
'allowActions' => [
|
||||
// 简单上传
|
||||
'name/cos:PutObject',
|
||||
'name/cos:PostObject',
|
||||
// 分片上传
|
||||
'name/cos:InitiateMultipartUpload',
|
||||
'name/cos:ListMultipartUploads',
|
||||
'name/cos:ListParts',
|
||||
'name/cos:UploadPart',
|
||||
'name/cos:CompleteMultipartUpload'
|
||||
]
|
||||
];
|
||||
// 获取临时密钥,计算签名
|
||||
$result = $sts->getTempKeys($config);
|
||||
$result['url'] = $this->uploadUrl . '/';
|
||||
$result['type'] = 'COS';
|
||||
$result['bucket'] = $this->storageName;
|
||||
$result['region'] = $this->storageRegion;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算临时密钥用的签名
|
||||
* @param $opt
|
||||
* @param $key
|
||||
* @param $method
|
||||
* @param $config
|
||||
* @return string
|
||||
*/
|
||||
public function getSignature($opt, $key, $method, $config)
|
||||
{
|
||||
$formatString = $method . $config['domain'] . '/?' . $this->json2str($opt, 1);
|
||||
$sign = hash_hmac('sha1', $formatString, $key);
|
||||
$sign = base64_encode($this->_hex2bin($sign));
|
||||
return $sign;
|
||||
}
|
||||
|
||||
public function _hex2bin($data)
|
||||
{
|
||||
$len = strlen($data);
|
||||
return pack("H" . $len, $data);
|
||||
}
|
||||
|
||||
// obj 转 query string
|
||||
public function json2str($obj, $notEncode = false)
|
||||
{
|
||||
ksort($obj);
|
||||
$arr = array();
|
||||
if (!is_array($obj)) {
|
||||
return $this->setError($obj . " must be a array");
|
||||
}
|
||||
foreach ($obj as $key => $val) {
|
||||
array_push($arr, $key . '=' . ($notEncode ? $val : rawurlencode($val)));
|
||||
}
|
||||
return join('&', $arr);
|
||||
}
|
||||
|
||||
// v2接口的key首字母小写,v3改成大写,此处做了向下兼容
|
||||
public function backwardCompat($result)
|
||||
{
|
||||
if (!is_array($result)) {
|
||||
return $this->setError($result . " must be a array");
|
||||
}
|
||||
$compat = array();
|
||||
foreach ($result as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$compat[lcfirst($key)] = $this->backwardCompat($value);
|
||||
} elseif ($key == 'Token') {
|
||||
$compat['sessionToken'] = $value;
|
||||
} else {
|
||||
$compat[lcfirst($key)] = $value;
|
||||
}
|
||||
}
|
||||
return $compat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 桶列表
|
||||
* @param string|null $region
|
||||
* @param bool $line
|
||||
* @param bool $shared
|
||||
* @return array|mixed
|
||||
* "Name" => "record-1254950941"
|
||||
* "Location" => "ap-chengdu"
|
||||
* "CreationDate" => "2019-05-16T08:33:29Z"
|
||||
* "BucketType" => "cos"
|
||||
*/
|
||||
public function listbuckets(string $region = null, bool $line = false, bool $shared = false)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->listBuckets();
|
||||
$bucket = [];
|
||||
if (isset($res['Buckets'][0]['Bucket'])) {
|
||||
if (isset($res['Buckets'][0]['Bucket']['Name'])) {
|
||||
$bucket[] = $res['Buckets'][0]['Bucket'];
|
||||
} else {
|
||||
$bucket = $res['Buckets'][0]['Bucket'];
|
||||
}
|
||||
} else if (isset($res['Buckets']['Bucket'])) {
|
||||
$bucket = $res['Buckets']['Bucket'];
|
||||
}
|
||||
return $bucket;
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @param string $acl public-read=公共独写
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function createBucket(string $name, string $region = '', string $acl = 'public-read')
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
$regionData = array_column($regionData, 'value');
|
||||
if (!in_array($region, $regionData)) {
|
||||
return $this->setError('COS:无效的区域!');
|
||||
}
|
||||
$this->storageRegion = $region;
|
||||
$app = $this->app();
|
||||
//检测桶
|
||||
try {
|
||||
$res1 = $app->headBucket($name, $region);
|
||||
|
||||
if ($res1 !== true) {
|
||||
return $this->setError('COS:设置的桶名已经存在!');
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
//桶不存在返回404
|
||||
if (strstr('404', $e->getMessage())) {
|
||||
return $this->setError('COS:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
//创建桶
|
||||
try {
|
||||
$res = $app->createBucket($name, $region, $acl);
|
||||
} catch (\Throwable $e) {
|
||||
if (strstr('[curl] 6', $e->getMessage())) {
|
||||
return $this->setError('COS:无效的区域!!');
|
||||
} else if (strstr('Access Denied.', $e->getMessage())) {
|
||||
return $this->setError('COS:无权访问');
|
||||
}
|
||||
return $this->setError('COS:' . $e->getMessage());
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除桶
|
||||
* @param string $name
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function deleteBucket(string $name)
|
||||
{
|
||||
try {
|
||||
$this->app()->deleteBucket($name);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string|null $region
|
||||
* @return array|object
|
||||
*/
|
||||
public function getDomian(string $name, string $region = null)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->getBucketDomain($name, $region);
|
||||
if (isset($res['DomainRule'])) {
|
||||
$domainRules[] = $res['DomainRule']['Name'];
|
||||
} else {
|
||||
$domainRules = array_column($res['DomainRules'], 'Name');
|
||||
}
|
||||
return $domainRules;
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定域名
|
||||
* @param string $name
|
||||
* @param string $domain
|
||||
* @param string|null $region
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
$this->storageRegion = $region;
|
||||
$parseDomin = parse_url($domain);
|
||||
try {
|
||||
$this->app()->putBucketDomain($name, $region, [
|
||||
'Name' => $parseDomin['host'],
|
||||
'Status' => 'ENABLED',
|
||||
'Type' => 'REST',
|
||||
'ForcedReplacement' => 'CNAME'
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
if ($message = $this->setMessage($e->getMessage())) {
|
||||
return $this->setError($message);
|
||||
}
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
protected function setMessage(string $message)
|
||||
{
|
||||
$data = [
|
||||
'The specified bucket does not exist.' => '指定的存储桶不存在。',
|
||||
'Please add CNAME/TXT record to DNS then try again later. Please allow up to 10 mins before your DNS takes effect.' => '请将CNAME记录添加到DNS,然后稍后重试。在DNS生效前,请等待最多10分钟。'
|
||||
];
|
||||
$msg = $data[$message] ?? '';
|
||||
if ($msg) {
|
||||
return $msg;
|
||||
}
|
||||
foreach ($data as $item) {
|
||||
if (strstr($message, $item)) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置跨域
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool
|
||||
*/
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
$this->storageRegion = $region;
|
||||
try {
|
||||
$this->app()->putBucketCors($name, [
|
||||
'AllowedHeader' => ['*'],
|
||||
'AllowedMethod' => ['PUT', 'GET', 'POST', 'DELETE', 'HEAD'],
|
||||
'AllowedOrigin' => ['*'],
|
||||
'ExposeHeader' => ['ETag', 'Content-Length', 'x-cos-request-id'],
|
||||
'MaxAgeSeconds' => 12
|
||||
], $region);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地域
|
||||
* @return mixed|\string[][]
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'ap-chengdu',
|
||||
'label' => '成都'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-shanghai',
|
||||
'label' => '上海'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-guangzhou',
|
||||
'label' => '广州'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-nanjing',
|
||||
'label' => '南京'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-beijing',
|
||||
'label' => '北京'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-chongqing',
|
||||
'label' => '重庆'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-shenzhen-fsi',
|
||||
'label' => '深圳金融'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-shanghai-fsi',
|
||||
'label' => '上海金融'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-beijing-fsi',
|
||||
'label' => '北京金融'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-hongkong',
|
||||
'label' => '中国香港'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-singapore',
|
||||
'label' => '新加坡'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-mumbai',
|
||||
'label' => '孟买'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-jakarta',
|
||||
'label' => '雅加达'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-seoul',
|
||||
'label' => '首尔'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-bangkok',
|
||||
'label' => '曼谷'
|
||||
],
|
||||
[
|
||||
'value' => 'ap-tokyo',
|
||||
'label' => '东京'
|
||||
],
|
||||
[
|
||||
'value' => 'na-siliconvalley',
|
||||
'label' => '硅谷(美西)'
|
||||
],
|
||||
[
|
||||
'value' => 'na-ashburn',
|
||||
'label' => '弗吉尼亚(美东)'
|
||||
],
|
||||
[
|
||||
'value' => 'na-toronto',
|
||||
'label' => '多伦多'
|
||||
],
|
||||
[
|
||||
'value' => 'sa-saopaulo',
|
||||
'label' => '圣保罗'
|
||||
],
|
||||
[
|
||||
'value' => 'eu-frankfurt',
|
||||
'label' => '法兰克福'
|
||||
],
|
||||
[
|
||||
'value' => 'eu-moscow',
|
||||
'label' => '莫斯科'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
527
crmeb/services/upload/storage/Jdoss.php
Normal file
527
crmeb/services/upload/storage/Jdoss.php
Normal file
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use Aws\Acm\Exception\AcmException;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\basic\BaseUpload;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
|
||||
/**
|
||||
* 京东云COS文件上传
|
||||
* Class Jdoss
|
||||
* @package crmeb\services\upload\storage
|
||||
*/
|
||||
class Jdoss extends BaseUpload
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 应用id
|
||||
* @var string
|
||||
*/
|
||||
protected $appid;
|
||||
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var S3Client
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cdn;
|
||||
|
||||
/**
|
||||
* 水印位置
|
||||
* @var string[]
|
||||
*/
|
||||
protected $position = [
|
||||
'1' => '1',//:左上
|
||||
'2' => '2',//:中上
|
||||
'3' => '3',//:右上
|
||||
'4' => '4',//:左中
|
||||
'5' => '5',//:中部
|
||||
'6' => '6',//:右中
|
||||
'7' => '7',//:左下
|
||||
'8' => '8',//:中下
|
||||
'9' => '9',//:右下
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '') ?: sys_config('site_url');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
$this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \crmeb\services\upload\extend\jdoss\Client
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
if (!$this->accessKey || !$this->secretKey) {
|
||||
throw new UploadException('Please configure accessKey and secretKey');
|
||||
}
|
||||
$this->handle = new \crmeb\services\upload\extend\jdoss\Client([
|
||||
'accessKey' => $this->accessKey,
|
||||
'secretKey' => $this->secretKey,
|
||||
]);
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param string $file
|
||||
* @return array|bool|mixed
|
||||
*/
|
||||
public function move(string $file = 'file')
|
||||
{
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('上传的文件不存在');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
$body = fopen($fileHandle->getRealPath(), 'rb');
|
||||
$body = (string)Utils::streamFor($body);
|
||||
try {
|
||||
$uploadInfo = $this->app()->putObject($this->storageName, $this->storageRegion, $key, [
|
||||
'body' => $body
|
||||
]);
|
||||
if (!$uploadInfo) {
|
||||
return $this->setError('Upload failure');
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
try {
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
$key = $this->getUploadPath($key);
|
||||
$fileContent = (string)Utils::streamFor($fileContent);
|
||||
$uploadInfo = $this->app()->putObject($this->storageName, $this->storageRegion, $key, [
|
||||
'body' => $fileContent
|
||||
]);
|
||||
if (!$uploadInfo) {
|
||||
return $this->setError('Upload failure');
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $key;
|
||||
$this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param string $key
|
||||
* @return array|bool|\crmeb\services\upload\extend\cos\SimpleXMLElement|mixed
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
try {
|
||||
return $this->app()->deleteObject($this->storageName, $this->storageRegion, $key);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $region
|
||||
* @param bool $line
|
||||
* @param bool $shared
|
||||
* @return array|\Aws\Result
|
||||
*/
|
||||
public function listbuckets(string $region = 'cn-north-1', bool $line = false, bool $shared = false)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->listBuckets();
|
||||
return $res['Buckets']['Bucket'] ?? [];
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @param string $acl
|
||||
* @return array|\AsyncAws\S3\Result\CreateBucketOutput|bool|\crmeb\services\upload\extend\cos\SimpleXMLElement
|
||||
*/
|
||||
public function createBucket(string $name, string $region = '', string $acl = 'public-read')
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
$regionData = array_column($regionData, 'value');
|
||||
if (!in_array($region, $regionData)) {
|
||||
return $this->setError('COS:无效的区域!');
|
||||
}
|
||||
$this->storageRegion = $region;
|
||||
$app = $this->app();
|
||||
//检测桶
|
||||
try {
|
||||
$app->headBucket($name, $region);
|
||||
} catch (\Throwable $e) {
|
||||
//桶不存在返回404
|
||||
if (strstr('404', $e->getMessage())) {
|
||||
return $this->setError('JDOSS:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
//创建桶
|
||||
try {
|
||||
$res = $app->createBucket($name, $region, $acl);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError('JDOSS:' . $e->getMessage());
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除桶
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteBucket(string $name, string $region = '')
|
||||
{
|
||||
try {
|
||||
$this->storageRegion = $region;
|
||||
$this->app()->deleteBucket($name, $region);
|
||||
return true;
|
||||
} catch (AcmException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'cn-north-1',
|
||||
'label' => '华北-北京'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-1',
|
||||
'label' => '华东-宿迁'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-2',
|
||||
'label' => '华东-上海'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-south-1',
|
||||
'label' => '华南-广州'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function getDomian(string $name, string $region = null)
|
||||
{
|
||||
try {
|
||||
$this->storageRegion = $region;
|
||||
$res = $this->app()->getBucketPolicy([
|
||||
'Bucket' => $name
|
||||
]);
|
||||
return $res['DomainName'] ?? [];
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
try {
|
||||
$this->storageRegion = $region;
|
||||
$this->app()->putBucketWebsite([
|
||||
'Bucket' => $name,
|
||||
'WebsiteConfiguration' => [
|
||||
'RedirectAllRequestsTo' => [
|
||||
'HostName' => $domain,
|
||||
'Protocol' => 'http'
|
||||
]
|
||||
]
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
$this->storageRegion = $region;
|
||||
try {
|
||||
$this->app()->putBucketCors($name, $region,
|
||||
['CORSConfiguration' => [ // REQUIRED
|
||||
'CORSRules' => [ // REQUIRED
|
||||
[
|
||||
'AllowedHeaders' => ['*'],
|
||||
'AllowedMethods' => ['POST', 'GET', 'PUT', 'DELETE', 'HEAD'], // REQUIRED
|
||||
'AllowedOrigins' => ['*'], // REQUIRED
|
||||
'ExposeHeaders' => ['Etag'],
|
||||
'MaxAgeSeconds' => 0
|
||||
],
|
||||
],
|
||||
]
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取OSS上传密钥
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function getTempKeys($key = '', $path = '', $contentType = '', $expires = '+10 minutes')
|
||||
{
|
||||
try {
|
||||
$app = $this->app();
|
||||
$host = $app->getRequestUrl() . '/' . $this->storageName;
|
||||
$url = 'https://' . $host . '/' . $key;
|
||||
$params = $this->getTempKeysParam($host, $url);
|
||||
$param = [];
|
||||
foreach ($params as $k => $value) {
|
||||
$param[] = $k . '=' . $value;
|
||||
}
|
||||
return [
|
||||
'upload_url' => $url . '?' . implode('&', $param),
|
||||
'type' => 'JDOSS',
|
||||
'url' => $this->uploadUrl . '/' . $key
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传需要参数
|
||||
* @param string $host
|
||||
* @return array
|
||||
*/
|
||||
public function getTempKeysParam(string $host, string $url)
|
||||
{
|
||||
$amzDate = gmdate('Ymd\THis\Z');
|
||||
$sdt = substr($amzDate, 0, 8);
|
||||
|
||||
$credentialScope = $sdt . '/' . $this->storageRegion . '/' . 's3' . '/aws4_request';
|
||||
|
||||
$clientHeader = [
|
||||
'Host' => $host,
|
||||
'X-Amz-Content-Sha256' => 'UNSIGNED-PAYLOAD',
|
||||
'X-Amz-Date' => $amzDate
|
||||
];
|
||||
$param = [
|
||||
'X-Amz-Content-Sha256' => 'UNSIGNED-PAYLOAD',
|
||||
'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
|
||||
'X-Amz-Credential' => $this->accessKey . '/' . $credentialScope,
|
||||
'X-Amz-Date' => $amzDate,
|
||||
'X-Amz-SignedHeaders' => 'host;X-Amz-Content-Sha25;X-Amz-Date',
|
||||
'X-Amz-Expires' => 600
|
||||
];
|
||||
[$canonicalRequest, $signedHeaders] = $this->app()->createCanonicalRequest($url, 'GET', $clientHeader, ['query' => $param]);
|
||||
|
||||
$stringToSign = "AWS4-HMAC-SHA256\n" . $amzDate . "\n" . $credentialScope . "\n" . hash('sha256', $canonicalRequest);
|
||||
|
||||
$key = $this->getSigningKey($sdt, $this->storageRegion, 's3', $this->accessKey);
|
||||
$signature = hash_hmac('sha256', $stringToSign, $key);
|
||||
|
||||
$param['X-Amz-Signature'] = $signature;
|
||||
return $param;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成key
|
||||
* @param $shortDate
|
||||
* @param $region
|
||||
* @param $service
|
||||
* @param $secretKey
|
||||
* @return false|string
|
||||
*/
|
||||
public function getSigningKey($shortDate, $region, $service, $secretKey)
|
||||
{
|
||||
$kSecret = 'AWS4' . $secretKey;
|
||||
$kDate = hash_hmac('sha256', $shortDate, $kSecret, true);
|
||||
$kRegion = hash_hmac('sha256', $region, $kDate, true);
|
||||
$kService = hash_hmac('sha256', $service, $kRegion, true);
|
||||
return hash_hmac('sha256', 'aws4_request', $kService, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $fileName
|
||||
* @param string $type
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $fileName = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
//x-oss-process=img/s/200/300
|
||||
if (sys_config('image_thumbnail_status', 1) && isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=img/s' . $config[$width] . '/' . $config[$height];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
} else {
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?x-oss-process') === false) {
|
||||
$filePath .= '?x-oss-process=img';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new AdminException('请先配置水印图片');
|
||||
}
|
||||
//x-oss-process=img/wmi/wk/ZG93bmxvYWRzOmxvZ28ucG5n/ws/100
|
||||
$waterPath = $filePath .= '/wmi/wk/' . base64_encode($waterConfig['watermark_image']) . '/wd/' . $waterConfig['watermark_opacity'] . '/wp/' . ($this->position[$waterConfig['watermark_position']] ?? '1') . '/wdx/' . $waterConfig['watermark_x'] . '/wdy/' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new AdminException('请先配置水印文字');
|
||||
}
|
||||
//?x-oss-process=img/wmt/wt/5Lqs5Lic5LqR
|
||||
$waterConfig['watermark_text_color'] = str_replace('#', '', $waterConfig['watermark_text_color']);
|
||||
$waterPath = $filePath .= '/wmt/wt/' . base64_encode($waterConfig['watermark_text']) . '/wc/' . $waterConfig['watermark_text_color'] . '/ws/' . $waterConfig['watermark_text_size'] . '/wp/' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . '/wdx/' . $waterConfig['watermark_x'] . '/wdy/' . $waterConfig['watermark_y'] . '/wr/' . $waterConfig['watermark_text_angle'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频封面图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @param int $time
|
||||
* @return array
|
||||
*/
|
||||
public function videoCoverImage(string $filePath = '', string $type = 'all', int $time = 1)
|
||||
{
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
//?x-oss-process=video/snapshot,t_7000,f_jpg,w_800,h_600,m_fast
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 600;
|
||||
$width = 400;
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=video/snapshot,t_' . ($time * 1000) . ',f_jpg,w_' . $width . ',h_' . $height . ',m_fast';
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
448
crmeb/services/upload/storage/Local.php
Normal file
448
crmeb/services/upload/storage/Local.php
Normal file
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\basic\BaseUpload;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\DownloadImageService;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Config;
|
||||
use think\facade\Filesystem;
|
||||
use think\File;
|
||||
use think\Image;
|
||||
|
||||
/**
|
||||
* 本地上传
|
||||
* Class Local
|
||||
* @package crmeb\services\upload\storage
|
||||
*/
|
||||
class Local extends BaseUpload
|
||||
{
|
||||
|
||||
/**
|
||||
* 默认存放路径
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultPath;
|
||||
|
||||
/**
|
||||
* 缩略图、水印图存放位置
|
||||
* @var string
|
||||
*/
|
||||
public $thumbWaterPath = 'thumb_water';
|
||||
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->defaultPath = Config::get('filesystem.disks.' . Config::get('filesystem.default') . '.url');
|
||||
$this->waterConfig['watermark_text_font'] = app()->getRootPath() . 'public' . '/statics/font/simsunb.ttf';
|
||||
}
|
||||
|
||||
protected function app()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function getTempKeys()
|
||||
{
|
||||
//
|
||||
return $this->setError('请检查您的上传配置,视频默认oss上传');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成上传文件目录
|
||||
* @param $path
|
||||
* @param null $root
|
||||
* @return string
|
||||
*/
|
||||
public function uploadDir($path, $root = null)
|
||||
{
|
||||
if ($root === null) $root = app()->getRootPath() . 'public/';
|
||||
return str_replace('\\', '/', $root . 'uploads/' . $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查上传目录不存在则生成
|
||||
* @param $dir
|
||||
* @return bool
|
||||
*/
|
||||
protected function validDir($dir)
|
||||
{
|
||||
return is_dir($dir) == true || mkdir($dir, 0777, true) == true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测filepath是否是远程地址
|
||||
* @param string $filePath
|
||||
* @return bool
|
||||
*/
|
||||
public function checkFilePathIsRemote(string $filePath)
|
||||
{
|
||||
return strpos($filePath, 'https:') !== false || strpos($filePath, 'http:') !== false || substr($filePath, 0, 2) === '//';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成与配置相关的文件名称以及路径
|
||||
* @param string $filePath 原地址
|
||||
* @param string $toPath 保存目录
|
||||
* @param array $config 配置相关参数
|
||||
* @param string $root
|
||||
* @return string
|
||||
*/
|
||||
public function createSaveFilePath(string $filePath, string $toPath, array $config = [], $root = '/')
|
||||
{
|
||||
[$path, $ext] = $this->getFileName($filePath);
|
||||
$fileName = md5(json_encode($config) . $filePath);
|
||||
return $this->uploadDir($toPath, $root) . '/' . $fileName . '.' . $ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* @param string $file
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function move(string $file = 'file', $realName = false)
|
||||
{
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('Upload file does not exist');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$stream = fopen($fileHandle->getPathname(), 'r');
|
||||
$content = (fread($stream, filesize($fileHandle->getPathname())));
|
||||
if (is_resource($stream)) {
|
||||
fclose($stream);
|
||||
}
|
||||
// if (preg_match('/think|log|phar|Socket|Channel|Flysystem|Psr6Cache|Cached|Request|debug|Psr6Cachepool|eval/i', $content)) {
|
||||
// return $this->setError('文件内容不合法');
|
||||
// }
|
||||
$fileType = pathinfo($fileHandle->getOriginalName(), PATHINFO_EXTENSION);
|
||||
$fileName = Filesystem::putFileAs($this->path, $fileHandle, in_array($fileType, ['ico']) ? $fileHandle->getOriginalName() : strtolower($fileHandle->hashName()));
|
||||
if (!$fileName)
|
||||
return $this->setError('Upload failure');
|
||||
$filePath = Filesystem::path($fileName);
|
||||
$this->fileInfo->uploadInfo = new File($filePath);
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->fileName = $this->fileInfo->uploadInfo->getFilename();
|
||||
$this->fileInfo->filePath = $this->defaultPath . '/' . str_replace('\\', '/', $fileName);
|
||||
if ($this->checkImage(root_path() . 'public' . $this->fileInfo->filePath) && $this->authThumb && $fileType != 'ico') {
|
||||
try {
|
||||
$this->thumb($this->fileInfo->filePath);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
return $this->fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
|
||||
$this->verifyExtension($key);
|
||||
|
||||
$dir = $this->uploadDir($this->path);
|
||||
if (!$this->validDir($dir)) {
|
||||
return $this->setError('Failed to generate upload directory, please check the permission!');
|
||||
}
|
||||
$fileName = $dir . '/' . $key;
|
||||
file_put_contents($fileName, $fileContent);
|
||||
$this->fileInfo->uploadInfo = new File($fileName);
|
||||
$this->fileInfo->realName = $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePath = $this->defaultPath . '/' . $this->path . '/' . $key;
|
||||
if ($this->checkImage(root_path() . 'public' . $this->fileInfo->filePath) && $this->authThumb) {
|
||||
try {
|
||||
$this->thumb($this->fileInfo->filePath);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
return $this->fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流下载保存图片
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function down(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
$dir = $this->uploadDir($this->path);
|
||||
if (!$this->validDir($dir)) {
|
||||
return $this->setError('Failed to generate upload directory, please check the permission!');
|
||||
}
|
||||
$fileName = $dir . '/' . $key;
|
||||
file_put_contents($fileName, $fileContent);
|
||||
$this->downFileInfo->downloadInfo = new File($fileName);
|
||||
$this->downFileInfo->downloadRealName = $key;
|
||||
$this->downFileInfo->downloadFileName = $key;
|
||||
$this->downFileInfo->downloadFilePath = $this->defaultPath . '/' . $this->path . '/' . $key;
|
||||
return $this->downFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缩略图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @return array|mixed|string[]
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath, true);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
//地址存在且不是远程地址
|
||||
if ($filePath && !$this->checkFilePathIsRemote($filePath) && strpos($filePath, 'uploads/' . $this->thumbWaterPath) === false) {
|
||||
$dir = $this->uploadDir($this->thumbWaterPath);
|
||||
if (!$this->validDir($dir)) {
|
||||
throw new ValidateException('缩略图生成目录生成失败,目录:' . $dir);
|
||||
}
|
||||
$filePath = $this->water($filePath);
|
||||
$this->fileInfo->filePathWater = $filePath;
|
||||
$config = $this->thumbConfig;
|
||||
try {
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
if (isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$savePath = $this->createSaveFilePath($filePath, $this->thumbWaterPath, [$height => $config[$height], $width => $config[$width]]);
|
||||
//防止重复生成
|
||||
if (!file_exists(root_path() . 'public' . $savePath)) {
|
||||
if ($this->authThumb) {//生成
|
||||
$Image = Image::open(app()->getRootPath() . 'public' . $filePath);
|
||||
$Image->thumb($config[$width], $config[$height])->save(root_path() . 'public' . $savePath);
|
||||
} else {
|
||||
$savePath = $filePath;
|
||||
}
|
||||
}
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$data[$v] = $this->fileInfo->$key = $savePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new ValidateException($e->getMessage());
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$waterPath = $filePath = $this->getFilePath($filePath);
|
||||
//地址存在且不是远程地址
|
||||
if ($filePath && !$this->checkFilePathIsRemote($filePath)) {
|
||||
$waterConfig = $this->waterConfig;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
$waterPath = $this->createSaveFilePath($filePath, $this->thumbWaterPath, $waterConfig);
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1:
|
||||
if ($waterConfig['watermark_image']) $waterPath = $this->image($filePath, $waterConfig, $waterPath);
|
||||
break;
|
||||
case 2:
|
||||
$waterPath = $this->text($filePath, $waterConfig, $waterPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片水印
|
||||
* @param string $filePath
|
||||
* @param array $waterConfig
|
||||
* @param string $waterPath
|
||||
* @return string
|
||||
*/
|
||||
public function image(string $filePath, array $waterConfig = [], string $waterPath = '')
|
||||
{
|
||||
if (!$waterConfig) {
|
||||
$waterConfig = $this->waterConfig;
|
||||
}
|
||||
$watermark_image = $waterConfig['watermark_image'];
|
||||
//远程图片
|
||||
if ($watermark_image && $this->checkFilePathIsRemote($watermark_image)) {
|
||||
//看是否在本地
|
||||
if (!$this->isLocal($watermark_image)) {//不再本地 继续下载
|
||||
[$p, $e] = $this->getFileName($watermark_image);
|
||||
$name = 'water_image_' . md5($watermark_image) . '.' . $e;
|
||||
$watermark_image = $this->defaultPath . '/' . $this->thumbWaterPath . '/' . $name;
|
||||
if (!file_exists(root_path() . 'public' . $watermark_image)) {
|
||||
try {
|
||||
/** @var DownloadImageService $down */
|
||||
$down = app()->make(DownloadImageService::class);
|
||||
$data = $down->path($this->thumbWaterPath)->downloadImage($waterConfig['watermark_image'], $name);
|
||||
$watermark_image = $data['path'] ?? '';
|
||||
} catch (\Throwable $e) {
|
||||
throw new ValidateException('远程水印图片下载失败,原因:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$watermark_image = $this->getFilePath($watermark_image, true);
|
||||
}
|
||||
}
|
||||
if (!$watermark_image) {
|
||||
throw new ValidateException('请先配置水印图片');
|
||||
}
|
||||
if (!$waterPath) {
|
||||
[$path, $ext] = $this->getFileName($filePath);
|
||||
$waterPath = $path . '_water_image.' . $ext;
|
||||
}
|
||||
try {
|
||||
if (!file_exists(root_path() . 'public' . $waterPath)) {
|
||||
$Image = Image::open(app()->getRootPath() . 'public' . $filePath);
|
||||
$Image->water(app()->getRootPath() . 'public' . $watermark_image, $waterConfig['watermark_position'] ?: 1, $waterConfig['watermark_opacity'])->save(root_path() . 'public' . $waterPath);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new ValidateException($e->getMessage());
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字水印
|
||||
* @param string $filePath
|
||||
* @param array $waterConfig
|
||||
* @param string $waterPath
|
||||
* @return string
|
||||
*/
|
||||
public function text(string $filePath, array $waterConfig = [], string $waterPath = '')
|
||||
{
|
||||
if (!$waterConfig) {
|
||||
$waterConfig = $this->waterConfig;
|
||||
}
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new ValidateException('请先配置水印文字');
|
||||
}
|
||||
if (!$waterPath) {
|
||||
[$path, $ext] = $this->getFileName($filePath);
|
||||
$waterPath = $path . '_water_text.' . $ext;
|
||||
}
|
||||
try {
|
||||
if (!file_exists(root_path() . 'public' . $waterPath)) {
|
||||
if ($this->authThumb) {//生成
|
||||
$Image = Image::open(app()->getRootPath() . 'public' . $filePath);
|
||||
if (strlen($waterConfig['watermark_text_color']) < 7) {
|
||||
$waterConfig['watermark_text_color'] = substr($waterConfig['watermark_text_color'], 1);
|
||||
$waterConfig['watermark_text_color'] = '#' . $waterConfig['watermark_text_color'] . $waterConfig['watermark_text_color'];
|
||||
}
|
||||
if (strlen($waterConfig['watermark_text_color']) > 7) {
|
||||
$waterConfig['watermark_text_color'] = substr($waterConfig['watermark_text_color'], 0, 7);
|
||||
}
|
||||
$Image->text($waterConfig['watermark_text'], $waterConfig['watermark_text_font'], $waterConfig['watermark_text_size'], $waterConfig['watermark_text_color'], $waterConfig['watermark_position'], [$waterConfig['watermark_x'], $waterConfig['watermark_y'], $waterConfig['watermark_text_angle']])->save(root_path() . 'public' . $waterPath);
|
||||
} else {
|
||||
$waterPath = $filePath;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new ValidateException($e->getMessage() . $e->getLine());
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param string $filePath
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function delete(string $filePath)
|
||||
{
|
||||
if (file_exists($filePath)) {
|
||||
try {
|
||||
unlink($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
//水印图片
|
||||
$waterPath = $this->createSaveFilePath($filePath, $this->thumbWaterPath, $waterConfig, null);
|
||||
if (file_exists($waterPath)) unlink($waterPath);
|
||||
$config = $this->thumbConfig;
|
||||
//缩略图
|
||||
foreach ($this->thumb as $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
if (isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$thumbPath = $this->createSaveFilePath($waterPath, $this->thumbWaterPath, [$height => $config[$height], $width => $config[$width]], null);
|
||||
if (file_exists($thumbPath)) unlink($thumbPath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function listbuckets(string $region, bool $line = false, bool $shared = false)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function createBucket(string $name, string $region)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function deleteBucket(string $name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRegion()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDomian(string $name, string $region = null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
385
crmeb/services/upload/storage/Obs.php
Normal file
385
crmeb/services/upload/storage/Obs.php
Normal file
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\upload\extend\obs\Client as TyClient;
|
||||
use crmeb\basic\BaseUpload;
|
||||
use DateTimeInterface;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Obs extends BaseUpload
|
||||
{
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var TyClient
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cdn;
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param bool $isStream
|
||||
* @param string|null $fileContent
|
||||
* @return array|bool|mixed
|
||||
*/
|
||||
public function move(string $file = 'file', bool $isStream = false, string $fileContent = null)
|
||||
{
|
||||
if (!$isStream) {
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('上传的文件不存在');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
|
||||
$body = fopen($fileHandle->getRealPath(), 'rb');
|
||||
$body = (string)Utils::streamFor($body);
|
||||
} else {
|
||||
$key = $file;
|
||||
$body = $fileContent;
|
||||
}
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
try {
|
||||
$uploadInfo = $this->app()->putObject($key, $body, 'application/octet-stream');
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
return $this->move($key, true, $fileContent);
|
||||
}
|
||||
|
||||
public function delete(string $filePath)
|
||||
{
|
||||
try {
|
||||
return $this->app()->deleteObject($filePath);
|
||||
} catch (\Exception $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
$this->cdn = $config['cdn'] ?? null;
|
||||
$this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化cos
|
||||
* @return TyClient
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
$this->handle = new TyClient([
|
||||
'accessKey' => $this->accessKey,
|
||||
'secretKey' => $this->secretKey,
|
||||
'region' => $this->storageRegion ?: 'cn-north-1',
|
||||
'bucket' => $this->storageName,
|
||||
'uploadUrl' => $this->uploadUrl
|
||||
]);
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function listbuckets(string $region = null, bool $line = false, bool $shared = false)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->listBuckets();
|
||||
return $res['Buckets']['Bucket'] ?? [];
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function createBucket(string $name, string $region, string $acl = TyClient::DEFAULT_OBS_ACL)
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
$regionData = array_column($regionData, 'value');
|
||||
if (!in_array($region, $regionData)) {
|
||||
return $this->setError('COS:无效的区域!');
|
||||
}
|
||||
$this->storageRegion = $region;
|
||||
$app = $this->app();
|
||||
//创建桶
|
||||
try {
|
||||
$app->createBucket($name, $region, $acl);
|
||||
$data = [
|
||||
'Statement' => [
|
||||
'Sid' => '公共读' . $name,
|
||||
'Effect' => 'Allow',
|
||||
'Principal' => [
|
||||
'ID' => ['*']
|
||||
],
|
||||
'Action' => ['HeadBucket', 'GetBucketLocation', 'ListBucketVersions', 'GetObject', 'RestoreObject', 'GetObjectVersion'],
|
||||
'Resource' => [$name, $name . '/*']
|
||||
]
|
||||
];
|
||||
|
||||
$app->putPolicy($name, $region, $data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError('COS:' . $e->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRegion()
|
||||
{
|
||||
return $this->app()->getRegion();
|
||||
}
|
||||
|
||||
public function deleteBucket(string $name, string $region = '')
|
||||
{
|
||||
try {
|
||||
$this->app()->deleteBucket($name, $region);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function getDomian($name, $region)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->getBucketDomain($name, $region);
|
||||
if ($res) {
|
||||
$domainRules = $res->toArray()['ListBucketCustomDomainsResult'];
|
||||
return array_column($domainRules, 'DomainName');
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
$parseDomin = parse_url($domain);
|
||||
try {
|
||||
$this->app()->putBucketDomain($name, $region, [
|
||||
'domainname' => $parseDomin['host'],
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
try {
|
||||
$this->app()->putBucketCors($name, $region, [
|
||||
'AllowedHeader' => ['*'],
|
||||
'AllowedMethod' => ['PUT', 'GET', 'POST', 'DELETE', 'HEAD'],
|
||||
'AllowedOrigin' => ['*'],
|
||||
'ExposeHeader' => ['ETag'],
|
||||
'MaxAgeSeconds' => 0
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @date 2023/6/13
|
||||
*/
|
||||
public function getTempKeys($callbackUrl = '', $dir = '')
|
||||
{
|
||||
// return [
|
||||
// 'access_key' => $this->accessKey,
|
||||
// 'secret_key' => $this->secretKey,
|
||||
// 'type' => 'OBS'
|
||||
// ];
|
||||
// TODO: Implement getTempKeys() method.
|
||||
$base64CallbackBody = base64_encode(json_encode([
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
|
||||
'callbackBodyType' => "application/x-www-form-urlencoded"
|
||||
]));
|
||||
|
||||
$policy = json_encode([
|
||||
'expiration' => $this->gmtIso8601(time() + 300),
|
||||
'conditions' =>
|
||||
[
|
||||
[0 => 'content-length-range', 1 => 0, 2 => 1048576000],
|
||||
['bucket' => $this->storageName],
|
||||
[0 => 'starts-with', 1 => '$key', 2 => $dir],
|
||||
]
|
||||
]);
|
||||
$base64Policy = base64_encode($policy);
|
||||
$signature = base64_encode(hash_hmac('sha1', $base64Policy, $this->secretKey, true));
|
||||
return [
|
||||
'accessid' => $this->accessKey,
|
||||
'host' => $this->uploadUrl,
|
||||
'policy' => $base64Policy,
|
||||
'signature' => $signature,
|
||||
'expire' => time() + 30,
|
||||
'callback' => $base64CallbackBody,
|
||||
'cdn' => $this->cdn,
|
||||
'type' => 'OBS'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ISO时间格式
|
||||
* @param $time
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function gmtIso8601($time): string
|
||||
{
|
||||
$dtStr = date("c", $time);
|
||||
$myDateTime = new \DateTime($dtStr);
|
||||
$expiration = $myDateTime->format(DateTimeInterface::ISO8601);
|
||||
$pos = strpos($expiration, '+');
|
||||
$expiration = substr($expiration, 0, $pos);
|
||||
return $expiration . "Z";
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $fileName
|
||||
* @param string $type
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $fileName = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
if (sys_config('image_thumbnail_status', 1) && isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=image/resize,h_' . $config[$height] . ',w_' . $config[$width];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
} else {
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?x-oss-process') === false) {
|
||||
$filePath .= '?x-oss-process=image';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new AdminException('请先配置水印图片');
|
||||
}
|
||||
$waterPath = $filePath .= '/watermark,image_' . base64_encode($waterConfig['watermark_image']) . ',t_' . $waterConfig['watermark_opacity'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new AdminException('请先配置水印文字');
|
||||
}
|
||||
$waterConfig['watermark_text_color'] = str_replace('#', '', $waterConfig['watermark_text_color']);
|
||||
$waterPath = $filePath .= '/watermark,text_' . base64_encode($waterConfig['watermark_text']) . ',color_' . $waterConfig['watermark_text_color'] . ',size_' . $waterConfig['watermark_text_size'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
}
|
||||
586
crmeb/services/upload/storage/Oss.php
Normal file
586
crmeb/services/upload/storage/Oss.php
Normal file
@@ -0,0 +1,586 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\basic\BaseUpload;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use OSS\Core\OssException;
|
||||
use OSS\Model\CorsConfig;
|
||||
use OSS\Model\CorsRule;
|
||||
use OSS\OssClient;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
|
||||
/**
|
||||
* 阿里云OSS上传
|
||||
* Class OSS
|
||||
*/
|
||||
class Oss extends BaseUpload
|
||||
{
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var \OSS\OssClient
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* 水印位置
|
||||
* @var string[]
|
||||
*/
|
||||
protected $position = [
|
||||
'1' => 'nw',//:左上
|
||||
'2' => 'north',//:中上
|
||||
'3' => 'ne',//:右上
|
||||
'4' => 'west',//:左中
|
||||
'5' => 'center',//:中部
|
||||
'6' => 'east',//:右中
|
||||
'7' => 'sw',//:左下
|
||||
'8' => 'south',//:中下
|
||||
'9' => 'se',//:右下
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
protected function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化oss
|
||||
* @return OssClient
|
||||
* @throws OssException
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
if (!$this->accessKey || !$this->secretKey) {
|
||||
throw new UploadException('Please configure accessKey and secretKey');
|
||||
}
|
||||
$this->handle = new OssClient($this->accessKey, $this->secretKey, $this->storageRegion);
|
||||
//不再自动创建
|
||||
// if (!$this->handle->doesBucketExist($this->storageName)) {
|
||||
// $this->handle->createBucket($this->storageName, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE);
|
||||
// }
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param string $file
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function move(string $file = 'file')
|
||||
{
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('Upload file does not exist');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
$key = $this->getUploadPath($key);
|
||||
try {
|
||||
$uploadInfo = $this->app()->putObject($this->storageName, $key, file_get_contents($fileHandle->getRealPath()));
|
||||
if (!isset($uploadInfo['info']['url'])) {
|
||||
return $this->setError('Upload failure');
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->filePath = $this->uploadUrl . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
try {
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
$this->verifyExtension($key);
|
||||
|
||||
$fileContent = (string)Utils::streamFor($fileContent);
|
||||
$uploadInfo = $this->app()->putObject($this->storageName, $key, $fileContent);
|
||||
if (!isset($uploadInfo['info']['url'])) {
|
||||
return $this->setError('Upload failure');
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $key;
|
||||
$this->fileInfo->filePath = $this->uploadUrl . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @return mixed|string[]
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
if (isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=image/resize,h_' . $config[$height] . ',w_' . $config[$width];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?x-oss-process') === false) {
|
||||
$filePath .= '?x-oss-process=image';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new ValidateException('请先配置水印图片');
|
||||
}
|
||||
$waterPath = $filePath .= '/watermark,image_' . base64_encode($waterConfig['watermark_image']) . ',t_' . $waterConfig['watermark_opacity'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new ValidateException('请先配置水印文字');
|
||||
}
|
||||
$waterConfig['watermark_text_color'] = str_replace('#', '', $waterConfig['watermark_text_color']);
|
||||
$waterPath = $filePath .= '/watermark,text_' . base64_encode($waterConfig['watermark_text']) . ',color_' . $waterConfig['watermark_text_color'] . ',size_' . $waterConfig['watermark_text_size'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频封面图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @param int $time
|
||||
* @return array
|
||||
*/
|
||||
public function videoCoverImage(string $filePath = '', string $type = 'all', int $time = 1)
|
||||
{
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
//?x-oss-process=video/snapshot,t_7000,f_jpg,w_800,h_600,m_fast
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 600;
|
||||
$width = 400;
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=video/snapshot,t_' . ($time * 1000) . ',f_jpg,w_' . $width . ',h_' . $height . ',m_fast';
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除资源
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
try {
|
||||
return $this->app()->deleteObject($this->storageName, $key);
|
||||
} catch (OssException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取OSS上传密钥
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function getTempKeys($callbackUrl = '', $dir = '')
|
||||
{
|
||||
//
|
||||
$base64CallbackBody = base64_encode(json_encode([
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
|
||||
'callbackBodyType' => "application/x-www-form-urlencoded"
|
||||
]));
|
||||
|
||||
$policy = json_encode([
|
||||
'expiration' => $this->gmtIso8601(time() + 30),
|
||||
'conditions' =>
|
||||
[
|
||||
[0 => 'content-length-range', 1 => 0, 2 => 1048576000],
|
||||
[0 => 'starts-with', 1 => '$key', 2 => $dir]
|
||||
]
|
||||
]);
|
||||
$base64Policy = base64_encode($policy);
|
||||
$signature = base64_encode(hash_hmac('sha1', $base64Policy, $this->secretKey, true));
|
||||
return [
|
||||
'accessid' => $this->accessKey,
|
||||
'host' => $this->uploadUrl,
|
||||
'policy' => $base64Policy,
|
||||
'signature' => $signature,
|
||||
'expire' => time() + 30,
|
||||
'callback' => $base64CallbackBody,
|
||||
'type' => 'OSS'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ISO时间格式
|
||||
* @param $time
|
||||
* @return string
|
||||
*/
|
||||
protected function gmtIso8601($time)
|
||||
{
|
||||
$dtStr = date("c", $time);
|
||||
$mydatetime = new \DateTime($dtStr);
|
||||
$expiration = $mydatetime->format(\DateTime::ISO8601);
|
||||
$pos = strpos($expiration, '+');
|
||||
$expiration = substr($expiration, 0, $pos);
|
||||
return $expiration . "Z";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前管理下的所有桶
|
||||
* @param string|null $region
|
||||
* @param bool $line
|
||||
* @param bool $shared
|
||||
* @return mixed|\OSS\Model\BucketListInfo
|
||||
* @throws OssException
|
||||
*/
|
||||
public function listbuckets(string $region = 'oss-cn-hangzhou.aliyuncs.com', bool $line = false, bool $shared = false)
|
||||
{
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
$response = $handle->listBuckets();
|
||||
$data = $response->getBucketList();
|
||||
$list = [];
|
||||
foreach ($data as $item) {
|
||||
$list[] = [
|
||||
'location' => $item->getLocation(),
|
||||
'name' => $item->getName(),
|
||||
'createTime' => $item->getCreateDate()
|
||||
];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @param string $acl
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function createBucket(string $name, string $region = '', string $acl = OssClient::OSS_ACL_TYPE_PUBLIC_READ)
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
if (!in_array($region, array_column($regionData, 'value'))) {
|
||||
return $this->setError('OSS:无效的区域');
|
||||
}
|
||||
try {
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
if ($handle->doesBucketExist($name)) {
|
||||
return $this->setError('OSS:空间已经存在,请更换空间名');
|
||||
}
|
||||
return $handle->createBucket($name, $acl);
|
||||
} catch (\Throwable $e) {
|
||||
if (strstr('The bucket you access does not belong to you', $e->getMessage())) {
|
||||
return $this->setError('OSS:空间已被使用,请更换空间名');
|
||||
}
|
||||
return $this->setError('OSS:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool|mixed|null
|
||||
*/
|
||||
public function deleteBucket(string $name, string $region = '')
|
||||
{
|
||||
try {
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
return $handle->deleteBucket($name);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string|null $region
|
||||
* @return array|bool
|
||||
*/
|
||||
public function getDomian(string $name, string $region = null)
|
||||
{
|
||||
try {
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
$res = $handle->getBucketCname($name);
|
||||
$data = $res->getCnames();
|
||||
return array_column($data, 'Domain');
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定域名
|
||||
* @param string $name
|
||||
* @param string $domain
|
||||
* @param string|null $region
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
$parseDomin = parse_url($domain);
|
||||
try {
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
$res = $handle->getBucketCname($name);
|
||||
$data = $res->getCnames();
|
||||
if (in_array($parseDomin['host'], array_column($data, 'Domain'))) {
|
||||
return true;
|
||||
}
|
||||
return $handle->addBucketCname($name, $parseDomin['host']);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置跨域
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool|null
|
||||
*/
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
try {
|
||||
$handle = new OssClient($this->accessKey, $this->secretKey, $region);
|
||||
$corsConfig = new CorsConfig();
|
||||
$rule = new CorsRule();
|
||||
// 设置允许跨域请求的响应头。AllowedHeader可以设置多个,每个AllowedHeader中最多只能使用一个通配符星号(*)。
|
||||
// 建议无特殊需求时设置AllowedHeader为星号(*)。
|
||||
$rule->addAllowedHeader("*");
|
||||
// 设置允许用户从应用程序中访问的响应头。ExposeHeader可以设置多个,ExposeHeader中不支持使用通配符星号(*)。
|
||||
$rule->addExposeHeader("ETag");
|
||||
// 设置允许的跨域请求的来源。AllowedOrigin可以设置多个,每个AllowedOrigin中最多只能使用一个通配符星号(*)。
|
||||
// 设置AllowedOrigin为星号(*)时,表示允许所有域的来源。
|
||||
$rule->addAllowedOrigin("*");
|
||||
// 设置允许的跨域请求方法。
|
||||
$rule->addAllowedMethod("POST");
|
||||
$rule->addAllowedMethod("GET");
|
||||
$rule->addAllowedMethod("DELETE");
|
||||
$rule->addAllowedMethod("PUT");
|
||||
$rule->addAllowedMethod("HEAD");
|
||||
// 设置浏览器对特定资源的预取(OPTIONS)请求返回结果的缓存时间,单位为秒。
|
||||
$rule->setMaxAgeSeconds(600);
|
||||
// 每个Bucket最多支持添加10条规则。
|
||||
$corsConfig->addRule($rule);
|
||||
return $handle->putBucketCors($name, $corsConfig);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据中心
|
||||
* @return mixed|\string[][]
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'oss-cn-hangzhou.aliyuncs.com',
|
||||
'label' => '华东1(杭州)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-shanghai.aliyuncs.com',
|
||||
'label' => '华东2(上海)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-qingdao.aliyuncs.com',
|
||||
'label' => '华北1(青岛)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-beijing.aliyuncs.com',
|
||||
'label' => '华北2(北京)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-zhangjiakou.aliyuncs.com',
|
||||
'label' => '华北 3(张家口)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-huhehaote.aliyuncs.com',
|
||||
'label' => '华北5(呼和浩特)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-wulanchabu.aliyuncs.com',
|
||||
'label' => '华北6(乌兰察布)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-shenzhen.aliyuncs.com',
|
||||
'label' => '华南1(深圳)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-heyuan.aliyuncs.com',
|
||||
'label' => '华南2(河源)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-guangzhou.aliyuncs.com',
|
||||
'label' => '华南3(广州)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-chengdu.aliyuncs.com',
|
||||
'label' => '西南1(成都)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-cn-hongkong.aliyuncs.com',
|
||||
'label' => '中国(香港)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-us-west-1.aliyuncs.com',
|
||||
'label' => '美国(硅谷)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-us-east-1.aliyuncs.com',
|
||||
'label' => '美国(弗吉尼亚)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-southeast-1.aliyuncs.com',
|
||||
'label' => '新加坡*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-southeast-2.aliyuncs.com',
|
||||
'label' => '澳大利亚(悉尼)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-southeast-3.aliyuncs.com',
|
||||
'label' => '马来西亚(吉隆坡)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-southeast-5.aliyuncs.com',
|
||||
'label' => '印度尼西亚(雅加达)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-northeast-1.aliyuncs.com',
|
||||
'label' => '日本(东京)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-south-1.aliyuncs.com',
|
||||
'label' => '印度(孟买)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-eu-central-1.aliyuncs.com',
|
||||
'label' => '德国(法兰克福)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-eu-west-1.aliyuncs.com',
|
||||
'label' => '英国(伦敦)'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-me-east-1.aliyuncs.com',
|
||||
'label' => '阿联酋(迪拜)*'
|
||||
],
|
||||
[
|
||||
'value' => 'oss-ap-southeast-6.aliyuncs.com',
|
||||
'label' => '菲律宾(马尼拉)'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
493
crmeb/services/upload/storage/Qiniu.php
Normal file
493
crmeb/services/upload/storage/Qiniu.php
Normal file
@@ -0,0 +1,493 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\basic\BaseUpload;
|
||||
use crmeb\exceptions\UploadException;
|
||||
use crmeb\services\HttpService;
|
||||
use Qiniu\Auth;
|
||||
use Qiniu\Http\Client;
|
||||
use Qiniu\Http\Error;
|
||||
use Qiniu\Storage\BucketManager;
|
||||
use Qiniu\Storage\UploadManager;
|
||||
use Qiniu\Config;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
|
||||
/**
|
||||
* 七牛云上传
|
||||
* Class Qiniu
|
||||
*/
|
||||
class Qiniu extends BaseUpload
|
||||
{
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var object
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* 水印位置
|
||||
* @var string[]
|
||||
*/
|
||||
protected $position = [
|
||||
'1' => 'NorthWest',//:左上
|
||||
'2' => 'North',//:中上
|
||||
'3' => 'NorthEast',//:右上
|
||||
'4' => 'West',//:左中
|
||||
'5' => 'Center',//:中部
|
||||
'6' => 'East',//:右中
|
||||
'7' => 'SouthWest',//:左下
|
||||
'8' => 'South',//:中下
|
||||
'9' => 'SouthEast',//:右下
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化七牛云
|
||||
* @return object|Auth
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
if (!$this->accessKey || !$this->secretKey) {
|
||||
throw new UploadException('Please configure accessKey and secretKey');
|
||||
}
|
||||
$this->handle = new Auth($this->accessKey, $this->secretKey);
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param string $file
|
||||
* @return array|bool|mixed|\StdClass|string
|
||||
*/
|
||||
public function move(string $file = 'file')
|
||||
{
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('Upload file does not exist');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
$key = $this->getUploadPath($key);
|
||||
$token = $this->app()->uploadToken($this->storageName);
|
||||
try {
|
||||
$uploadMgr = new UploadManager();
|
||||
[$result, $error] = $uploadMgr->putFile($token, $key, $fileHandle->getRealPath());
|
||||
if ($error !== null) {
|
||||
return $this->setError($error->message());
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $result;
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->filePath = $this->uploadUrl . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return array|bool|mixed|\StdClass
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
$this->verifyExtension($key);
|
||||
|
||||
$token = $this->app()->uploadToken($this->storageName, $key);
|
||||
try {
|
||||
$uploadMgr = new UploadManager();
|
||||
[$result, $error] = $uploadMgr->put($token, $key, $fileContent);
|
||||
if ($error !== null) {
|
||||
return $this->setError($error->message());
|
||||
}
|
||||
$this->fileInfo->uploadInfo = $result;
|
||||
$this->fileInfo->realName = $key;
|
||||
$this->fileInfo->filePath = $this->uploadUrl . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (UploadException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @return mixed|string[]
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
if (isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?imageView2/2/w/' . $config[$width] . '/h/' . $config[$height];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?') === false) {
|
||||
$filePath .= '?watermark';
|
||||
} else {
|
||||
$filePath .= '&watermark';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new ValidateException('请先配置水印图片');
|
||||
}
|
||||
$waterPath = $filePath .= '/1/image/' . base64_encode($waterConfig['watermark_image']) . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'SouthEest') . '/dissolve/' . $waterConfig['watermark_opacity'] . '/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new ValidateException('请先配置水印文字');
|
||||
}
|
||||
$waterPath = $filePath .= '/2/text/' . base64_encode($waterConfig['watermark_text']) . '/fill/' . base64_encode($waterConfig['watermark_text_color']) . '/fontsize/' . $waterConfig['watermark_text_size'] . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'SouthEest') . '/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频封面图
|
||||
* @param string $filePath
|
||||
* @param string $type
|
||||
* @param int $time
|
||||
* @return array
|
||||
*/
|
||||
public function videoCoverImage(string $filePath = '', string $type = 'all', int $time = 1)
|
||||
{
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
//?fops=vframe/jpg/offset/7/w/480/h/360
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 600;
|
||||
$width = 400;
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
$this->fileInfo->$key = $filePath . '?fops=vframe/jpg/offset/' . $time . '/w/' . $width . '/h/' . $height;
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传配置信息
|
||||
* @return array
|
||||
*/
|
||||
public function getSystem()
|
||||
{
|
||||
$token = $this->app()->uploadToken($this->storageName);
|
||||
$domain = $this->uploadUrl;
|
||||
$key = $this->saveFileName();
|
||||
return compact('token', 'domain', 'key');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除资源
|
||||
* @param $key
|
||||
* @param $bucket
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(string $key)
|
||||
{
|
||||
$bucketManager = new BucketManager($this->app(), new Config());
|
||||
return $bucketManager->delete($this->storageName, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛云上传密钥
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getTempKeys()
|
||||
{
|
||||
$token = $this->app()->uploadToken($this->storageName);
|
||||
$domain = $this->uploadUrl;
|
||||
$key = $this->saveFileName(NULL, 'mp4');
|
||||
$type = 'QINIU';
|
||||
return compact('token', 'domain', 'key', 'type');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前所有桶列表
|
||||
* @param string|null $region
|
||||
* @param bool $line
|
||||
* @param bool $shared
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function listbuckets(string $region = null, bool $line = false, bool $shared = false)
|
||||
{
|
||||
$bucket = new BucketManager($this->app());
|
||||
[$response, $error] = $bucket->listbuckets($region, $line ? 'true' : 'false', $shared ? 'true' : 'false');
|
||||
if ($error !== null) {
|
||||
return $this->setError($error->message());
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function createBucket(string $name, string $region = 'z0')
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
if (!in_array($region, array_column($regionData, 'value'))) {
|
||||
return $this->setError('七牛云:无效的区域');
|
||||
}
|
||||
$url = 'https://' . Config::UC_HOST . '/mkbucketv3/' . $name . '/region/' . $region;
|
||||
$body = null;
|
||||
$headers = $this->app()->authorizationV2($url, 'POST', $body, 'application/json');
|
||||
$headers["Content-Type"] = 'application/json';
|
||||
$ret = Client::post($url, $body, $headers);
|
||||
if (!$ret->ok()) {
|
||||
$error = new Error($url, $ret);
|
||||
if ('bucket exists' === $error->message()) {
|
||||
return $this->setError('七牛云:云空间已存在');
|
||||
}
|
||||
return $this->setError('七牛云:' . $error->message());
|
||||
}
|
||||
return ($ret->body === null) ? array() : $ret->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域
|
||||
* @return mixed|\string[][]
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'z0',
|
||||
'label' => '华东'
|
||||
],
|
||||
[
|
||||
'value' => 'z1',
|
||||
'label' => '华北'
|
||||
],
|
||||
[
|
||||
'value' => 'z2',
|
||||
'label' => '华南'
|
||||
],
|
||||
[
|
||||
'value' => 'na0',
|
||||
'label' => '北美'
|
||||
],
|
||||
[
|
||||
'value' => 'as0',
|
||||
'label' => '东南亚'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-east-2',
|
||||
'label' => '华东-浙江2'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除空间
|
||||
* @param string $name
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function deleteBucket(string $name)
|
||||
{
|
||||
$bucket = new BucketManager($this->app());
|
||||
[$response, $error] = $bucket->deleteBucket($name);
|
||||
if ($error !== null) {
|
||||
return $this->setError($error->message());
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛域名
|
||||
* @param string $name
|
||||
* @return array|bool|mixed|null
|
||||
*/
|
||||
public function getDomian(string $name)
|
||||
{
|
||||
$url = 'https://' . Config::UC_HOST . '/v2/domains?tbl=' . $name;
|
||||
$body = null;
|
||||
$headers = $this->app()->authorizationV2($url, 'POST', $body, 'application/x-www-form-urlencoded');
|
||||
$headers["Content-Type"] = 'application/x-www-form-urlencoded';
|
||||
$ret = Client::post($url, $body, $headers);
|
||||
if (!$ret->ok()) {
|
||||
$error = new Error($url, $ret);
|
||||
return $this->setError('七牛云:' . $error->message());
|
||||
}
|
||||
return ($ret->body === null) ? array() : $ret->json();
|
||||
}
|
||||
|
||||
public function getDomianInfo(string $host)
|
||||
{
|
||||
$url = 'https://' . Config::API_HOST . '/domain/' . $host;
|
||||
$headers = $this->app()->authorization($url, null, 'application/x-www-form-urlencoded');
|
||||
$headers["Content-Type"] = 'application/x-www-form-urlencoded';
|
||||
$ret = Client::get($url, $headers);
|
||||
if (!$ret->ok()) {
|
||||
$error = new Error($url, $ret);
|
||||
return $this->setError('七牛云:' . $error->message());
|
||||
}
|
||||
return ($ret->body === null) ? array() : $ret->json();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $domain
|
||||
* @param string|null $region
|
||||
* @return array|bool|mixed|null
|
||||
*/
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
$parseDomin = parse_url($domain);
|
||||
$url = 'https://' . Config::API_HOST . '/domain/' . $parseDomin['host'];
|
||||
$body = [
|
||||
'type' => 'normal',
|
||||
'platform' => 'web',
|
||||
'geocover' => 'china',
|
||||
'source' => [
|
||||
'sourceType' => 'qiniuBucket',
|
||||
'sourceQiniuBucket' => $name,
|
||||
'TestURLPath' => 'qiniu_do_not_delete.gif',
|
||||
],
|
||||
'protocol' => $parseDomin['scheme'],
|
||||
'cache' => [
|
||||
'cacheControls' => [
|
||||
[
|
||||
'time' => 1,
|
||||
'timeunit' => 4,
|
||||
'type' => 'all',
|
||||
'rule' => '*'
|
||||
]
|
||||
],
|
||||
'ignoreParam' => false
|
||||
]
|
||||
];
|
||||
$bodyJson = json_encode($body);
|
||||
$headers = $this->app()->authorization($url, $bodyJson, 'application/json');
|
||||
$headers["Content-Type"] = 'application/json';
|
||||
$ret = Client::post($url, $bodyJson, $headers);
|
||||
if (!$ret->ok()) {
|
||||
$error = new Error($url, $ret);
|
||||
return $this->setError('七牛云:' . $error->message());
|
||||
}
|
||||
return ($ret->body === null) ? array() : $ret->json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域
|
||||
* @param string $name
|
||||
* @param string $region
|
||||
* @return bool
|
||||
*/
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
513
crmeb/services/upload/storage/Tyoss.php
Normal file
513
crmeb/services/upload/storage/Tyoss.php
Normal file
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
|
||||
namespace crmeb\services\upload\storage;
|
||||
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\services\upload\extend\obs\Client as TyClient;
|
||||
use crmeb\basic\BaseUpload;
|
||||
use DateTimeInterface;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Tyoss extends BaseUpload
|
||||
{
|
||||
/**
|
||||
* accessKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $accessKey;
|
||||
|
||||
/**
|
||||
* secretKey
|
||||
* @var mixed
|
||||
*/
|
||||
protected $secretKey;
|
||||
|
||||
/**
|
||||
* 句柄
|
||||
* @var TyClient
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* 空间域名 Domain
|
||||
* @var mixed
|
||||
*/
|
||||
protected $uploadUrl;
|
||||
|
||||
/**
|
||||
* 存储空间名称 公开空间
|
||||
* @var mixed
|
||||
*/
|
||||
protected $storageName;
|
||||
|
||||
/**
|
||||
* COS使用 所属地域
|
||||
* @var mixed|null
|
||||
*/
|
||||
protected $storageRegion;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cdn;
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param bool $isStream
|
||||
* @param string|null $fileContent
|
||||
* @return array|bool|mixed
|
||||
*/
|
||||
public function move(string $file = 'file', bool $isStream = false, string $fileContent = null)
|
||||
{
|
||||
if (!$isStream) {
|
||||
$fileHandle = app()->request->file($file);
|
||||
if (!$fileHandle) {
|
||||
return $this->setError('上传的文件不存在');
|
||||
}
|
||||
if ($this->validate) {
|
||||
try {
|
||||
$error = [
|
||||
$file . '.filesize' => 'Upload filesize error',
|
||||
$file . '.fileExt' => 'Upload fileExt error',
|
||||
$file . '.fileMime' => 'Upload fileMine error'
|
||||
];
|
||||
validate([$file => $this->validate], $error)->check([$file => $fileHandle]);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
$key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
|
||||
|
||||
$body = fopen($fileHandle->getRealPath(), 'rb');
|
||||
$body = (string)Utils::streamFor($body);
|
||||
} else {
|
||||
$key = $file;
|
||||
$body = $fileContent;
|
||||
}
|
||||
$key = $this->getUploadPath($key);
|
||||
|
||||
try {
|
||||
$uploadInfo = $this->app()->putObject($key, $body, 'application/octet-stream');
|
||||
$this->fileInfo->uploadInfo = $uploadInfo;
|
||||
$this->fileInfo->realName = $fileHandle->getOriginalName();
|
||||
$this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
|
||||
$this->fileInfo->fileName = $key;
|
||||
$this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
|
||||
$this->authThumb && $this->thumb($this->fileInfo->filePath);
|
||||
return $this->fileInfo;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件流上传
|
||||
* @param string $fileContent
|
||||
* @param string|null $key
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function stream(string $fileContent, string $key = null)
|
||||
{
|
||||
if (!$key) {
|
||||
$key = $this->saveFileName();
|
||||
}
|
||||
return $this->move($key, true, $fileContent);
|
||||
}
|
||||
|
||||
public function delete(string $filePath)
|
||||
{
|
||||
try {
|
||||
return $this->app()->deleteObject($filePath);
|
||||
} catch (\Exception $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param array $config
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function initialize(array $config)
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->accessKey = $config['accessKey'] ?? null;
|
||||
$this->secretKey = $config['secretKey'] ?? null;
|
||||
$this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
|
||||
$this->storageName = $config['storageName'] ?? null;
|
||||
$this->storageRegion = $config['storageRegion'] ?? null;
|
||||
$this->cdn = $config['cdn'] ?? null;
|
||||
$this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化cos
|
||||
* @return TyClient
|
||||
*/
|
||||
protected function app()
|
||||
{
|
||||
$this->handle = new TyClient([
|
||||
'accessKey' => $this->accessKey,
|
||||
'secretKey' => $this->secretKey,
|
||||
'region' => $this->storageRegion ?: 'cn-qhxn1',
|
||||
'bucket' => $this->storageName,
|
||||
'uploadUrl' => $this->uploadUrl,
|
||||
'type' => 'ty'
|
||||
]);
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function listbuckets(string $region = null, bool $line = false, bool $shared = false)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->listBuckets();
|
||||
return $res['Buckets']['Bucket'] ?? [];
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function createBucket(string $name, string $region, string $acl = TyClient::DEFAULT_OBS_ACL)
|
||||
{
|
||||
$regionData = $this->getRegion();
|
||||
$regionData = array_column($regionData, 'value');
|
||||
if (!in_array($region, $regionData)) {
|
||||
return $this->setError('COS:无效的区域!');
|
||||
}
|
||||
$this->storageRegion = $region;
|
||||
$app = $this->app();
|
||||
//创建桶
|
||||
try {
|
||||
$app->createBucket($name, $region, $acl);
|
||||
$data = [
|
||||
'Statement' => [
|
||||
'Sid' => '公共读' . $name,
|
||||
'Effect' => 'Allow',
|
||||
'Principal' => [
|
||||
'ID' => ['*']
|
||||
],
|
||||
'Action' => ['HeadBucket', 'GetBucketLocation', 'ListBucketVersions', 'GetObject', 'RestoreObject', 'GetObjectVersion'],
|
||||
'Resource' => [$name, $name . '/*']
|
||||
]
|
||||
];
|
||||
|
||||
$app->putPolicy($name, $region, $data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError('COS:' . $e->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRegion()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'value' => 'cn-gz1',
|
||||
'label' => '贵州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-fz1',
|
||||
'label' => '福州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hz1',
|
||||
'label' => '杭州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-sz1',
|
||||
'label' => '深圳'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-gdgz1',
|
||||
'label' => '广州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-jssz1',
|
||||
'label' => '苏州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-sh1',
|
||||
'label' => '上海'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-ahwh1',
|
||||
'label' => '芜湖'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-bj1',
|
||||
'label' => '北京'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-sccd1',
|
||||
'label' => '成都'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hazz1',
|
||||
'label' => '郑州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hncs1',
|
||||
'label' => '长沙'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-gxnn1',
|
||||
'label' => '南宁'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-jxnc1',
|
||||
'label' => '南昌'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-sdqd1',
|
||||
'label' => '青岛'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-snxy1',
|
||||
'label' => '咸阳'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-xjcj1',
|
||||
'label' => '新疆'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-ynkm1',
|
||||
'label' => '昆明'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hihk1',
|
||||
'label' => '海口'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hbwh1',
|
||||
'label' => '武汉'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-cq1',
|
||||
'label' => '重庆'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-qhxn1',
|
||||
'label' => '西宁'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-gslz1',
|
||||
'label' => '兰州'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-nxyc1',
|
||||
'label' => '银川'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-sxty1',
|
||||
'label' => '太原'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hesjz1',
|
||||
'label' => '石家庄'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-tj1',
|
||||
'label' => '天津'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-jlcc1',
|
||||
'label' => '长春'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-hlhrb1',
|
||||
'label' => '哈尔滨'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-nmhh1',
|
||||
'label' => '内蒙古'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-lnsy1',
|
||||
'label' => '沈阳'
|
||||
],
|
||||
[
|
||||
'value' => 'cn-north1',
|
||||
'label' => '华北'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function deleteBucket(string $name, string $region = '')
|
||||
{
|
||||
try {
|
||||
$this->app()->deleteBucket($name, $region);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function getDomian($name, $region)
|
||||
{
|
||||
try {
|
||||
$res = $this->app()->getBucketDomain($name, $region);
|
||||
if ($res) {
|
||||
$domainRules = $res->toArray()['ListBucketCustomDomainsResult'];
|
||||
return array_column($domainRules, 'DomainName');
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public function bindDomian(string $name, string $domain, string $region = null)
|
||||
{
|
||||
$parseDomin = parse_url($domain);
|
||||
try {
|
||||
$this->app()->putBucketDomain($name, $region, [
|
||||
'domainname' => $parseDomin['host'],
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function setBucketCors(string $name, string $region)
|
||||
{
|
||||
try {
|
||||
$this->app()->putBucketCors($name, $region, [
|
||||
'AllowedHeader' => ['*'],
|
||||
'AllowedMethod' => ['PUT', 'GET', 'POST', 'DELETE', 'HEAD'],
|
||||
'AllowedOrigin' => ['*'],
|
||||
'ExposeHeader' => ['ETag'],
|
||||
'MaxAgeSeconds' => 0
|
||||
]);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->setError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $callbackUrl
|
||||
* @param string $dir
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @author 吴汐
|
||||
* @email 442384644@qq.com
|
||||
* @date 2023/06/19
|
||||
*/
|
||||
public function getTempKeys($callbackUrl = '', $dir = '')
|
||||
{
|
||||
// TODO: Implement getTempKeys() method.
|
||||
$base64CallbackBody = base64_encode(json_encode([
|
||||
'callbackUrl' => $callbackUrl,
|
||||
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
|
||||
'callbackBodyType' => "application/x-www-form-urlencoded"
|
||||
]));
|
||||
|
||||
$policy = json_encode([
|
||||
'expiration' => $this->gmtIso8601(time() + 300),
|
||||
'conditions' =>
|
||||
[
|
||||
[0 => 'content-length-range', 1 => 0, 2 => 1048576000],
|
||||
['bucket' => $this->storageName],
|
||||
[0 => 'starts-with', 1 => '$key', 2 => $dir],
|
||||
]
|
||||
]);
|
||||
$base64Policy = base64_encode($policy);
|
||||
$signature = base64_encode(hash_hmac('sha1', $base64Policy, $this->secretKey, true));
|
||||
return [
|
||||
'accessid' => $this->accessKey,
|
||||
'host' => $this->uploadUrl,
|
||||
'policy' => $base64Policy,
|
||||
'signature' => $signature,
|
||||
'expire' => time() + 30,
|
||||
'callback' => $base64CallbackBody,
|
||||
'cdn' => $this->cdn,
|
||||
'type' => 'OBS'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ISO时间格式
|
||||
* @param $time
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function gmtIso8601($time): string
|
||||
{
|
||||
$dtStr = date("c", $time);
|
||||
$myDateTime = new \DateTime($dtStr);
|
||||
$expiration = $myDateTime->format(DateTimeInterface::ISO8601);
|
||||
$pos = strpos($expiration, '+');
|
||||
$expiration = substr($expiration, 0, $pos);
|
||||
return $expiration . "Z";
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩略图
|
||||
* @param string $filePath
|
||||
* @param string $fileName
|
||||
* @param string $type
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function thumb(string $filePath = '', string $fileName = '', string $type = 'all')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
|
||||
$this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
|
||||
if ($filePath) {
|
||||
$config = $this->thumbConfig;
|
||||
foreach ($this->thumb as $v) {
|
||||
if ($type == 'all' || $type == $v) {
|
||||
$height = 'thumb_' . $v . '_height';
|
||||
$width = 'thumb_' . $v . '_width';
|
||||
$key = 'filePath' . ucfirst($v);
|
||||
if (sys_config('image_thumbnail_status', 1) && isset($config[$height]) && isset($config[$width]) && $config[$height] && $config[$width]) {
|
||||
$this->fileInfo->$key = $filePath . '?x-oss-process=image/resize,h_' . $config[$height] . ',w_' . $config[$width];
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
} else {
|
||||
$this->fileInfo->$key = $this->water($this->fileInfo->$key);
|
||||
$data[$v] = $this->fileInfo->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印
|
||||
* @param string $filePath
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function water(string $filePath = '')
|
||||
{
|
||||
$filePath = $this->getFilePath($filePath);
|
||||
$waterConfig = $this->waterConfig;
|
||||
$waterPath = $filePath;
|
||||
if ($waterConfig['image_watermark_status'] && $filePath) {
|
||||
if (strpos($filePath, '?x-oss-process') === false) {
|
||||
$filePath .= '?x-oss-process=image';
|
||||
}
|
||||
switch ($waterConfig['watermark_type']) {
|
||||
case 1://图片
|
||||
if (!$waterConfig['watermark_image']) {
|
||||
throw new AdminException('请先配置水印图片');
|
||||
}
|
||||
$waterPath = $filePath .= '/watermark,image_' . base64_encode($waterConfig['watermark_image']) . ',t_' . $waterConfig['watermark_opacity'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
case 2://文字
|
||||
if (!$waterConfig['watermark_text']) {
|
||||
throw new AdminException('请先配置水印文字');
|
||||
}
|
||||
$waterConfig['watermark_text_color'] = str_replace('#', '', $waterConfig['watermark_text_color']);
|
||||
$waterPath = $filePath .= '/watermark,text_' . base64_encode($waterConfig['watermark_text']) . ',color_' . $waterConfig['watermark_text_color'] . ',size_' . $waterConfig['watermark_text_size'] . ',g_' . ($this->position[$waterConfig['watermark_position']] ?? 'nw') . ',x_' . $waterConfig['watermark_x'] . ',y_' . $waterConfig['watermark_y'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $waterPath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user