不能一股脑安装所有适配器,因flysystem v3各适配器配置结构不一致、连接池不共享,且初始化参数格式不同(如七牛要求bucket全小写、oss需显式传usepathstyleendpoint),混用易导致类名冲突、凭证混用或连接错误。

直接在 composer.json 里同时 require 多个云存储 SDK(比如 overtrue/flysystem-qiniu、overtrue/flysystem-oss、league/flysystem-aws-s3-v3)是可行的,但必须隔离初始化逻辑——否则类名冲突、凭证混用、连接复用错误会立刻暴露。
为什么不能把所有 Flysystem 适配器一股脑装上就完事?
Flysystem v3 的适配器之间不共享连接池,也不共用配置结构。比如:
-
overtrue/flysystem-qiniu要求bucket全小写且不含区域后缀(如my-bucket-z1错,my-bucket对),而overtrue/flysystem-oss的endpoint必须带https://前缀,且region字段为空或填oss-cn-hangzhou,填cn-hangzhou就连不上 - 所有适配器都依赖
league/flysystem核心,但各自构造Filesystem实例时传参格式不一致:七牛要new QiniuAdapter(...)再传给new Filesystem(),OSS 则需先 newOssAdapter()并显式传入usePathStyleEndpoint - 若项目里某处误用
file_put_contents('qiniu://xxx', $data)却没注册 Stream Wrapper,会静默失败或抛出Warning: file_put_contents(): Unable to find the wrapper "qiniu"
如何用 Composer 控制多存储的加载与切换?
核心原则:运行时按需加载,不提前实例化,靠配置驱动初始化。不是“全装上”,而是“装好但不启动”。
- 在
composer.json中只声明抽象层和全部适配器:"league/flysystem": "^3.0"+ 所有需要的overtrue/flysystem-*或league/flysystem-aws-s3-v3 - 真实客户端实例由工厂类根据
config('filesystems.disks.current')动态创建,避免new QiniuAdapter()出现在业务代码里 - 本地开发可只装
league/flysystem+league/flysystem-local,CI/CD 阶段再通过COMPOSER_REQUIRE环境变量追加云适配器,例如:COMPOSER_REQUIRE="overtrue/flysystem-qiniu" composer install - 禁用
autoload-dev里的云 SDK —— 它们不是开发依赖,而是部署环境依赖
上传文件时最容易踩的坑是什么?
ThinkPHP/Laravel 的 $request->file() 返回的是框架封装对象,不是原生 SplFileInfo;直接丢给 Filesystem::write() 会写空文件或报错。
- 必须用
$file->getRealPath()获取临时路径,再file_get_contents()读取内容;若返回false(Swoole 场景常见),fallback 到$file->read() - 大文件(>2MB)禁止一次性
read(),应改用Filesystem::writeStream()+fopen($file->getRealPath(), 'r') - 写入前务必生成唯一文件名(如
sha1_file($file->getRealPath()) . '.' . $file->getExtension()),避免覆盖或冲突 - 禁止用
$file->getPathname()或$file->__toString()当路径传——Flysystem 不识别该路径格式,且受open_basedir限制
要不要引入 flysystem-stream-wrapper?
只在你无法改造存量代码时才考虑。它能让 file_get_contents('s3://bucket/key') 这种写法生效,但代价明显:
- 必须手动调用
StreamWrapper::register(),且只能注册一次,多注册会警告 - 不支持并发上传(
stream_select不兼容) - 异常类型被吞掉,原本
League\Flysystem\FilesystemException会变成Warning或静默失败 - 它绕过了 Flysystem 的缓存、事件、日志等机制,调试困难
真正需要多源切换的项目,应该统一走 Filesystem 实例接口,而不是迁就旧的 file_* 函数。那条“无痛迁移”的路,往往最痛的地方在后期维护。











