Files

65 lines
2.7 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Service\common\upload;
use App\Service\common\UtilsService;
/**
* ListBucketResult 解析
*
* 阿里云 GET Bucket、腾讯云 GET Bucket、S3 ListObjectsV2 回的都是同一套
* <ListBucketResult><Contents> 结构只有续拉游标的节点名不同NextMarker /
* NextContinuationToken。三个驱动共用本 trait,避免同一段 XML 遍历写三遍。
* 使用方需要有 $this->config 以及 ObjectKeyNormalizeTrait 提供的 key 规整方法。
*/
trait ObjectListXmlTrait
{
/**
* @param string $xml 响应体
* @param string $urlBase 未配置 domain 时兜底拼地址用的主机前缀(不带尾斜杠)
* @param string $tokenNode 续拉游标节点名
* @return array{items: array<int, array{key:string,size:int,hash:string,last_modified:int,url:string}>, next_marker: string, finished: bool}
*/
protected function parseObjectListXml(string $xml, string $urlBase, string $tokenNode = 'NextMarker'): array
{
$doc = @simplexml_load_string($xml);
if ($doc === false) {
UtilsService::getInstance()->errorThrow('列举结果解析失败,返回内容不是合法 XML');
}
$items = [];
$lastRawKey = '';
$contents = isset($doc->Contents) ? $doc->Contents : [];
foreach ($contents as $node) {
$rawKey = (string) $node->Key;
// 续拉游标必须用服务端原样返回的 key不能用补过 prefix 的规整值
$lastRawKey = $rawKey;
$key = $this->normalizeObjectKey($rawKey);
if ($key === '' || str_ends_with($key, '/')) {
// 以 / 结尾的是控制台建目录留下的占位对象,不是素材
continue;
}
$items[] = [
'key' => $key,
'size' => (int) $node->Size,
'hash' => strtolower(trim((string) $node->ETag, '"')),
'last_modified' => (int) strtotime((string) $node->LastModified),
'url' => $this->publicUrlOf($key, $urlBase . '/' . $key),
];
}
$truncated = filter_var((string) ($doc->IsTruncated ?? 'false'), FILTER_VALIDATE_BOOLEAN);
$next = isset($doc->{$tokenNode}) ? (string) $doc->{$tokenNode} : '';
if ($truncated && $next === '' && $tokenNode === 'NextMarker') {
// 部分兼容实现只给 IsTruncated 不给 NextMarker按协议可用本页最后一个 key 续拉
$next = $lastRawKey;
}
return [
'items' => $items,
'next_marker' => $truncated ? $next : '',
'finished' => !$truncated || $next === '',
];
}
}