Files
douba_shop/crmeb/services/upload/storage/Oss.php
Karlcarlo c9a98f342c chore: 添加静态资源文件和版本配置文件
添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材
添加系统版本配置文件(.version)和常量定义文件(.constant)
更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件
添加验证码配置文件(captcha.php)和.gitignore规则
2026-03-06 20:14:23 +08:00

587 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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' => '菲律宾(马尼拉)'
]
];
}
}