Files
zyt/server/app/common/service/storage/engine/Qcloud.php
T
long 7d7a4aa4d2 oss 直传
分片上传
2026-05-08 10:49:16 +08:00

239 lines
7.3 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
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-1300000000appId 即末段
$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 级 ListMultipartUploadscos-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;
}
}
}