239 lines
7.3 KiB
PHP
239 lines
7.3 KiB
PHP
<?php
|
||
|
||
namespace app\common\service\storage\engine;
|
||
|
||
use Exception;
|
||
use Qcloud\Cos\Client;
|
||
use QCloud\COSSTS\Sts;
|
||
|
||
/**
|
||
* 腾讯云存储引擎 (COS)
|
||
* Class Qiniu
|
||
* @package app\common\library\storage\engine
|
||
*/
|
||
class Qcloud extends Server
|
||
{
|
||
private $config;
|
||
private $cosClient;
|
||
|
||
/**
|
||
* 构造方法
|
||
* Qcloud constructor.
|
||
* @param $config
|
||
*/
|
||
public function __construct($config)
|
||
{
|
||
parent::__construct();
|
||
$this->config = $config;
|
||
// 创建COS控制类
|
||
$this->createCosClient();
|
||
}
|
||
|
||
/**
|
||
* 创建COS控制类
|
||
*/
|
||
private function createCosClient()
|
||
{
|
||
$this->cosClient = new Client([
|
||
'region' => $this->config['region'],
|
||
'credentials' => [
|
||
'secretId' => $this->config['access_key'],
|
||
'secretKey' => $this->config['secret_key'],
|
||
],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 执行上传
|
||
* @param $save_dir (保存路径)
|
||
* @return bool|mixed
|
||
*/
|
||
public function upload($save_dir)
|
||
{
|
||
// 上传文件
|
||
// putObject(上传接口,最大支持上传5G文件)
|
||
try {
|
||
$result = $this->cosClient->putObject([
|
||
'Bucket' => $this->config['bucket'],
|
||
'Key' => $save_dir . '/' . $this->fileName,
|
||
'Body' => fopen($this->getRealPath(), 'rb')
|
||
]);
|
||
return true;
|
||
} catch (Exception $e) {
|
||
$this->error = $e->getMessage();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* notes: 抓取远程资源(最大支持上传5G文件)
|
||
* @param $url
|
||
* @param null $key
|
||
* @author 张无忌(2021/3/2 14:36)
|
||
* @return mixed|void
|
||
*/
|
||
public function fetch($url, $key=null) {
|
||
try {
|
||
$this->cosClient->putObject([
|
||
'Bucket' => $this->config['bucket'],
|
||
'Key' => $key,
|
||
'Body' => fopen($url, 'rb')
|
||
]);
|
||
return true;
|
||
} catch (Exception $e) {
|
||
$this->error = $e->getMessage();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除文件
|
||
* @param $fileName
|
||
* @return bool|mixed
|
||
*/
|
||
public function delete($fileName)
|
||
{
|
||
try {
|
||
$this->cosClient->deleteObject(array(
|
||
'Bucket' => $this->config['bucket'],
|
||
'Key' => $fileName
|
||
));
|
||
return true;
|
||
} catch (Exception $e) {
|
||
$this->error = $e->getMessage();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 返回文件路径
|
||
* @return mixed
|
||
*/
|
||
public function getFileName()
|
||
{
|
||
return $this->fileName;
|
||
}
|
||
|
||
/**
|
||
* @notes 获取 STS 临时凭证(用于浏览器直传)
|
||
* @param string $keyPrefix 资源前缀,如 uploads/video/20260508/
|
||
* @param int $maxSizeBytes 单文件大小上限(字节)
|
||
* @param int $durationSeconds 凭证有效期(秒)
|
||
* @return array {credentials, expiredTime, requestId}
|
||
* @throws Exception
|
||
*/
|
||
public function getStsCredentials(string $keyPrefix, int $maxSizeBytes, int $durationSeconds = 1800): array
|
||
{
|
||
$bucket = $this->config['bucket'];
|
||
// bucket 形如 likeadmin-1300000000,appId 即末段
|
||
$appId = '';
|
||
if (strpos($bucket, '-') !== false) {
|
||
$parts = explode('-', $bucket);
|
||
$appId = end($parts);
|
||
}
|
||
if (!$appId || !ctype_digit($appId)) {
|
||
throw new Exception('腾讯云 COS bucket 名称不合法,无法解析 appId(期望形如 name-1300000000)');
|
||
}
|
||
|
||
$shortBucket = substr($bucket, 0, strrpos($bucket, '-'));
|
||
$region = $this->config['region'];
|
||
$prefix = ltrim($keyPrefix, '/');
|
||
if ($prefix === '' || substr($prefix, -1) !== '/') {
|
||
$prefix = $prefix . '/';
|
||
}
|
||
|
||
$duration = max(900, min($durationSeconds, 7200));
|
||
|
||
// 自行构造 policy:对象级写动作收紧 + bucket 级 ListMultipartUploads(cos-js-sdk-v5 续传探测必需)
|
||
$objectArn = sprintf('qcs::cos:%s:uid/%s:%s/%s*', $region, $appId, $bucket, $prefix);
|
||
$bucketArn = sprintf('qcs::cos:%s:uid/%s:%s/*', $region, $appId, $bucket);
|
||
|
||
$policy = [
|
||
'version' => '2.0',
|
||
'statement' => [
|
||
[
|
||
'effect' => 'allow',
|
||
'action' => [
|
||
'name/cos:PostObject',
|
||
'name/cos:PutObject',
|
||
'name/cos:InitiateMultipartUpload',
|
||
'name/cos:UploadPart',
|
||
'name/cos:CompleteMultipartUpload',
|
||
'name/cos:AbortMultipartUpload',
|
||
'name/cos:ListParts',
|
||
],
|
||
'resource' => [$objectArn],
|
||
'condition' => [
|
||
'numeric_less_than_equal' => [
|
||
'cos:content-length' => $maxSizeBytes,
|
||
],
|
||
],
|
||
],
|
||
[
|
||
// ListMultipartUploads 是 bucket 级操作,无法用对象级 ARN 命中
|
||
'effect' => 'allow',
|
||
'action' => ['name/cos:ListMultipartUploads'],
|
||
'resource' => [$bucketArn],
|
||
],
|
||
],
|
||
];
|
||
|
||
$config = [
|
||
'url' => 'https://sts.tencentcloudapi.com/',
|
||
'domain' => 'sts.tencentcloudapi.com',
|
||
'proxy' => '',
|
||
'secretId' => $this->config['access_key'],
|
||
'secretKey' => $this->config['secret_key'],
|
||
'bucket' => $bucket,
|
||
'region' => $region,
|
||
'durationSeconds' => $duration,
|
||
'policy' => $policy,
|
||
];
|
||
|
||
$sts = new Sts();
|
||
$result = $sts->getTempKeys($config);
|
||
if (!is_array($result) || empty($result['credentials'])) {
|
||
throw new Exception('获取 STS 临时凭证失败');
|
||
}
|
||
|
||
return [
|
||
'credentials' => [
|
||
'tmpSecretId' => $result['credentials']['tmpSecretId'] ?? '',
|
||
'tmpSecretKey' => $result['credentials']['tmpSecretKey'] ?? '',
|
||
'sessionToken' => $result['credentials']['sessionToken'] ?? '',
|
||
],
|
||
'expiredTime' => (int)($result['expiredTime'] ?? (time() + $duration)),
|
||
'startTime' => (int)($result['startTime'] ?? time()),
|
||
'bucket' => $bucket,
|
||
'shortBucket' => $shortBucket,
|
||
'region' => $region,
|
||
'appId' => $appId,
|
||
'host' => sprintf('https://%s.cos.%s.myqcloud.com', $bucket, $region),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @notes HEAD 校验对象是否真实存在并返回 size / contentType
|
||
* @param string $key
|
||
* @return array|false
|
||
*/
|
||
public function headObject(string $key)
|
||
{
|
||
try {
|
||
$result = $this->cosClient->headObject([
|
||
'Bucket' => $this->config['bucket'],
|
||
'Key' => $key,
|
||
]);
|
||
return [
|
||
'size' => (int)($result['ContentLength'] ?? 0),
|
||
'content_type' => (string)($result['ContentType'] ?? ''),
|
||
'etag' => trim((string)($result['ETag'] ?? ''), '"'),
|
||
];
|
||
} catch (Exception $e) {
|
||
$this->error = $e->getMessage();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
}
|