chore: 添加静态资源文件和版本配置文件

添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材
添加系统版本配置文件(.version)和常量定义文件(.constant)
更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件
添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
2026-03-06 20:14:23 +08:00
commit c9a98f342c
4485 changed files with 490986 additions and 0 deletions

View 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' => '莫斯科'
]
];
}
}

View 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;
}
}

View 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 [];
}
}

View 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;
}
}

View 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' => '菲律宾(马尼拉)'
]
];
}
}

View 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;
}
}

View 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;
}
}