云存储无真实文件夹,PHP需用putObject上传空对象模拟创建,deleteObjects批量删除前缀匹配对象;判断存在需listObjects查前缀或headObject查文件,注意各厂商SDK路径、分页、超时等差异。

PHP怎么用SDK创建和删除云存储文件夹
云存储(比如阿里云OSS、腾讯云COS)本身没有“文件夹”这个实体,所谓文件夹只是对象键名(Key)里带/的前缀。PHP SDK不提供单独的createFolder()函数,所有操作都靠putObject()或deleteObjects()模拟。
常见错误是调用putObject()时只传一个结尾带/的Key(如"uploads/2024/"),却不传Body——这在OSS会失败,在COS可能静默忽略,导致你以为创建成功了,其实根本没写入任何东西。
- 要“创建文件夹”,必须显式上传一个空对象:
putObject("bucket", "uploads/2024/", "") - 删除文件夹,本质是批量删除以该前缀开头的所有
Key,需先listObjects()再deleteObjects(),不能只删那个"uploads/2024/"空对象 - OSS的
listObjects默认只返回100条,分页逻辑漏掉会导致删不干净;COS的listObjectsV2接口更稳定,建议优先用
PHP判断云存储路径是否存在(伪文件夹 or 伪文件)
没有file_exists()那样的直接判断方式。你得靠listObjects()查前缀,再看结果是否为空——但要注意:查"logs/"能返回空对象(即“文件夹”),查"logs/app.log"才是查具体文件。
容易踩的坑是把headObject()当成万能判断:它对真实文件有效,但对"logs/"这种前缀会直接抛NoSuchKey异常(因为那不是对象),而你如果只捕获404却没处理403或网络超时,就会误判。
立即学习“PHP免费学习笔记(深入)”;
- 判断“文件夹”存在:调用
listObjects(["prefix" => "logs/", "max-keys" => 1]),检查Contents或CommonPrefixes是否非空 - 判断“文件”存在:用
headObject(),但必须捕获Exception并区分Code字段,"NoSuchKey"才代表不存在 - 不要用
file_get_contents("oss://...")来判断——PHP流封装器默认不支持目录列表,且多数云厂商已弃用或限制该功能
PHP批量操作云存储路径时的性能陷阱
一次删1000个文件,如果用foreach循环逐个deleteObject(),网络开销和SDK锁竞争会让耗时飙升到分钟级。同样,遍历所有"data/"下的文件做重命名,也别手动拼新Key再逐个copyObject()。
真正高效的做法是:用服务端批量接口 + 合理分页。OSS的deleteObjects()最多一次删1000个,COS的deleteObjects上限是100;超出就得拆包。但很多人忘了listObjects返回的NextMarker或ContinuationToken必须原样传给下一页,字符串编码或截断会导致漏数据。
- 批量删除:先
listObjects()拿到全部Keys,按100或1000切片,每片调一次deleteObjects() - 批量复制/移动:没有原子“重命名文件夹”操作,只能
copyObject()+deleteObject(),且必须确保copy成功后再删原对象,否则中间出错就丢数据 - 注意SDK默认超时是60秒,大量对象操作前记得调大
timeout和connect_timeout配置项
不同云厂商PHP SDK对路径处理的差异点
OSS、COS、七牛Kodo的SDK看着相似,但在路径处理上埋了不少隐性差异。比如OSS的listObjects返回的Key带前导/(如"/images/logo.png"),而COS默认不带;七牛的listPrefix接口返回的key字段甚至可能被自动URL解码,遇到含空格或中文的路径就错乱。
最常被忽略的是Endpoint和Region配置。写死"oss-cn-hangzhou.aliyuncs.com"没问题,但若用"oss-cn-hangzhou-internal.aliyuncs.com"内网地址连公网Bucket,或Region填错(如把"ap-guangzhou"写成"gz"),listObjects可能返回空结果,你还以为路径不存在。
- OSS SDK中
prefix参数值**不能以/开头**,否则匹配失效;COS则允许 - COS SDK的
listObjectsV2要求必须传Bucket和Prefix,少一个就报InvalidArgument - 七牛PHP SDK的
listPrefix()默认只返回100条,且marker参数必须用上一次响应里的marker字段原值,不能自己拼











