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