1. 问题
OSS PHP-SDK 手册中给出使用 listObjects
函数获取某一 Bucket 下文件列表的方法,如下(摘自 OSS PHP-SDK 文档):
$prefix = 'dir/';
$delimiter = '/';
$nextMarker = '';
$maxkeys = 30;
while (true) {
$options = array(
'delimiter' => $delimiter,
'prefix' => $prefix,
'max-keys' => $maxkeys,
'marker' => $nextMarker,
);
var_dump($options);
try {
$listObjectInfo = $ossClient->listObjects($bucket, $options);
} catch (OssException $e) {
printf(__FUNCTION__ . ": FAILED\n");
printf($e->getMessage() . "\n");
return;
}
// 得到nextMarker,从上一次listObjects读到的最后一个文件的下一个文件开始继续获取文件列表
$nextMarker = $listObjectInfo->getNextMarker();
$listObject = $listObjectInfo->getObjectList();
$listPrefix = $listObjectInfo->getPrefixList();
var_dump($listObject); // 这里手册中写的是输出 count($listObject),以下同理
var_dump($listPrefix);
if ($nextMarker === '') {
break;
}
}
这段代码的执行结果并没有返回我最开始想象中的文件名称、大小之类的数组,而是在浏览器输出了一些对象,那么这里该如何转换成我们需要的数据格式呢?此外,$options
数组中的 delimiter
,marker
键值对到底有什么作用呢?
2. 返回值
通过查看 var_dump
输出信息,可以发现执行结果为 OSSModelObjectInfo
对象和 OSSModelPrefixInfo
对象,分别对应 var_dump($listObject)
和 var_dump($listPrefix)
。
通过查看 PHP-SDK 源码,可以看到 OSSModelObjectInfo
中含有 getKey()
,getLastModified()
等方法,因而可以借此获取我们想得到的数据格式,如下:
/* 解析 prefixInfo 类 */
private function prefixInfoParse(PrefixInfo $prefixInfo){
return [
'dir' => $prefixInfo->getPrefix(),
];
}
/* 解析 objectInfo 类 */
public function objectInfoParse(ObjectInfo $objectInfo) {
return [
'name' => $objectInfo->getKey(),
'size' => $objectInfo->getSize(),
'update_at' => $objectInfo->getLastModified(),
];
}
3. options 数组键值
阿里云 OSS 中不支持原生的文件夹,而是使用一个 0 字节的末尾为
/
的文件起到文件夹功能。
$options
数组有四个键值对,其中
prefix
是我们想获取的文件的目录,如test/
即为列出目录test
下的所有文件及子文件夹(不递归获取);delimiter
为行使文件夹功能的分割符号,如/
;max-keys
是限定返回的文件和文件夹数目,这里不是指每次最多返回max-keys
值的数据,而是以这个值为分页的单页容量。如文件夹下有 100 个文件,这里设定max-keys
为 30,则执行后的返回结果为:['30 个 ObjectInfo 对象', '30 个 ObjectInfo 对象', '30 个 ObjectInfo 对象', '10 个 ObjectInfo 对象'];marker
是实现分页时指向下一分页起始位置的标识。
4. 总结
我们可以改写获取文件列表的方法,使其能够返回一个格式合适的数组:
public function fileList($dir, $maxKey = 30, $delimiter = '/', $nextMarker = '') {
$fileList = []; // 获取的文件列表, 数组的一阶表示分页结果
$dirList = []; // 获取的目录列表, 数组的一阶表示分页结果
$storageList = [
'file' => [], // 真正的文件数组
'dir' => [], // 真正的目录数组
];
while (true) {
$options = [
'delimiter' => $delimiter,
'prefix' => $dir,
'max-keys' => $maxKey,
'marker' => $nextMarker,
];
try {
$fileListInfo = $this->ossClient->listObjects($this->bucket, $options);
// 得到nextMarker, 从上一次 listObjects 读到的最后一个文件的下一个文件开始继续获取文件列表, 类似分页
} catch (OssException $e) {
return $this->send($this->errorCode, $e->getMessage()); // 发送错误信息
}
$nextMarker = $fileListInfo->getNextMarker();
$fileItem = $fileListInfo->getObjectList();
$dirItem = $fileListInfo->getPrefixList();
$fileList[] = $fileItem;
$dirList[] = $dirItem;
if ($nextMarker === '') break;
}
foreach ($fileList[0] as $item){
$storageList['file'][] = $this->objectInfoParse($item);
}
foreach ($dirList[0] as $item){
$storageList['dir'][] = $this->prefixInfoParse($item);
}
return $this->send($this->successCode, $storageList); // 发送正确信息
}
执行这一方法可以返回一个 json,格式如下:
{
"status": 200,
"message": {
"file": [
{
"name": "robotac2016/_ide_helper.php",
"size": 382295,
"update_at": "2016-09-20T13:45:04.000Z"
},
{
"name": "robotac2016/test.php",
"size": 11,
"update_at": "2016-09-20T12:06:36.000Z"
}
],
"dir": [
{
"dir": "robotac2016/innocence/"
}
]
}
}
5. 备忘
若想获取某一 Bucket 下的所有文件和文件夹,应该传入的
$prefix
值为空字符串''
而不是根目录符号/
,因为阿里云 OSS 本身没有文件夹概念,传入/
后系统会寻找根目录下所有首字母为/
的文件,而不是遍历根目录本身;是在阿里云 OSS 中,删除文件、创建文件操作中若删除的文件不存在或者创建的文件已存在时,OSS 不会返回错误提示,而是静默或替换同名文件。个人认为删除操作的执行准则是使得 OSS 中不具有某一文件而不是删掉它,而创建文件的原则是静默替换。所以在执行这些操作的时候,应该实现检测该文件是否存在以免造成不可恢复的损失。
使用 RAM 访问控制对 OSS 进行权限管理的时候,如果使用自定义的权限策略,该策略只会在使用 SDK 或 API 的时候体现。如现在自定义一个读写 OSS 下某一 Bucket 的权限策略,这一策略在 SDK 中是可以正常起到限制作用的,而在 RAM 控制台中,却被提示没有 OSS 的访问权限。猜测可能是阿里云 OSS 服务对控制台下的权限管理还是以服务为单位,目前还没有把权限精确到 OSS 的单个 Bucket 中。
{
"Statement": [
{
"Action": "oss:*",
"Effect": "Allow",
"Resource": [
"acs:oss:*:*:oss-test",
"acs:oss:*:*:oss-test/*"
]
}
],
"Version": "1"
}
2016.9.22 更新
关于备忘第二点中 RAM 访问控制的问题,我提交了一份工单给 OSS,得到的反馈是,如果想要在 RAM 控制台中以图形化界面访问 Bucket,需要在 Statement
中再添加一个策略,其 Action
值为 ListBuckets
,单独给出一个 *
权限是不够的,如下:
{
"Statement": [
// 策略 1:用于 api 权限限制
{
"Action": "oss:*",
"Effect": "Allow",
"Resource": [
"acs:oss:*:*:oss-test",
"acs:oss:*:*:oss-test/*"
]
},
// 策略 2:用于 RAM 控制台显示 Bucket 列表
{
"Action": [
"oss:ListBuckets"
],
"Effect": "Allow",
"Resource": [
"acs:oss:*:*:*"
]
}
],
"Version": "1"
}
在 ListBuckets
这一策略中,Resource
值只能是 acs:oss:*:*:*
而不能是 acs:oss:*:*:oss-test
,不然还是会在 RAM 控制台提示没有访问权限。虽然写成这样会使得登录 RAM 控制台的人看到所有的 Buckets,但其对除 oss-test
之外的 Buckets 是没有读写权限的。
6. 参考
阿里云访问控制帮助文档