├── .github └── FUNDING.yml ├── FileUploadAction.php ├── OSS.php ├── README.md └── composer.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: yiier 4 | custom: ['https://blog-1251237404.cos.ap-guangzhou.myqcloud.com/20190424153510.png', 'https://blog-1251237404.cos.ap-guangzhou.myqcloud.com/20190424153431.png'] 5 | -------------------------------------------------------------------------------- /FileUploadAction.php: -------------------------------------------------------------------------------- 1 | 4 | * createTime : 2019-08-04 09:16 5 | * description: 6 | */ 7 | 8 | namespace yiier\AliyunOSS; 9 | 10 | use Yii; 11 | use yii\base\Action; 12 | use yii\base\DynamicModel; 13 | use yii\base\InvalidConfigException; 14 | use yii\helpers\FileHelper; 15 | use yii\web\Response; 16 | use yii\web\UploadedFile; 17 | 18 | class FileUploadAction extends Action 19 | { 20 | /** 21 | * 上传文件的 file 参数名 22 | * @var string 23 | */ 24 | public $fileParam = 'filename'; 25 | 26 | /** 27 | * @link https://www.yiiframework.com/doc/guide/2.0/en/input-validation#ad-hoc-validation 28 | * @var array 29 | */ 30 | public $validationRules = [ 31 | [ 32 | 'file', 33 | 'file', 34 | 'extensions' => ['png', 'jpeg', 'jpg', 'gif', 'webp', 'bmp'], 35 | 'checkExtensionByMimeType' => false, 36 | 'mimeTypes' => 'image/*', 37 | 'maxSize' => 5 * 1024 * 1024 38 | ] 39 | ]; 40 | 41 | /** 42 | * 文件名生成的方式,默认用 md5 43 | * @var callable 44 | */ 45 | public $fileSaveNameCallback; 46 | 47 | /** 48 | * 文件保存的方法,默认用 UploadedFile::saveAs() 49 | * @var callable 50 | */ 51 | public $saveFileCallback; 52 | 53 | /** 54 | * 返回结果回调函数 55 | * @var callable 56 | */ 57 | public $returnCallback; 58 | 59 | /** 60 | * @var bool 61 | */ 62 | public $normalizePath = 'auto'; 63 | 64 | /** 65 | * 文件保存路径 66 | * @var string 67 | */ 68 | public $savePath = '@webroot/uploads'; 69 | 70 | /** 71 | * 文件显示的路径 72 | * @var string 73 | */ 74 | public $webPath = '@web/uploads'; 75 | 76 | /** 77 | * @var string 78 | */ 79 | public $uploadSaveErrorMassage = '上传的文件保存失败'; 80 | 81 | /** 82 | * 是否保留本地文件 83 | * @var bool 84 | */ 85 | public $keepLocalFile = false; 86 | 87 | /** 88 | * @inheritdoc 89 | */ 90 | public function init() 91 | { 92 | Yii::$app->response->format = Response::FORMAT_JSON; 93 | Yii::$app->request->enableCsrfValidation = false; 94 | if ($this->normalizePath === 'auto') { 95 | $this->normalizePath = strpos($this->savePath, '..') !== false; 96 | } 97 | parent::init(); 98 | } 99 | 100 | /** 101 | * @inheritdoc 102 | * 103 | * @return array 104 | * @throws InvalidConfigException 105 | */ 106 | public function run() 107 | { 108 | $uploadedFiles = UploadedFile::getInstancesByName($this->fileParam); 109 | $resultData = []; 110 | foreach ($uploadedFiles as $key => $uploadedFile) { 111 | $validationModel = DynamicModel::validateData(['file' => $uploadedFile], $this->validationRules); 112 | if ($validationModel->hasErrors()) { 113 | $errorMassage = $validationModel->getFirstError('file'); 114 | break; 115 | } 116 | try { 117 | $filename = $this->getFileName($uploadedFile); 118 | if (!$this->saveFile($uploadedFile, $filename)) { 119 | throw new \Exception($this->uploadSaveErrorMassage); 120 | } 121 | $this->uploadOSS($filename); 122 | if (!$this->keepLocalFile) { 123 | $this->deleteLocalFile($filename); 124 | } 125 | $resultData[$key] = $this->getFullFilename($filename, $this->webPath); 126 | continue; 127 | } catch (\Exception $e) { 128 | Yii::error($e, 'upload error'); 129 | $errorMassage = $e->getMessage(); 130 | break; 131 | } 132 | } 133 | 134 | if (isset($errorMassage)) { 135 | // 失败了要删除之前的 136 | foreach ($resultData as $resultDatum) { 137 | $this->deleteFile($resultDatum); 138 | } 139 | return $this->parseResult(500, $errorMassage); 140 | } 141 | return $this->parseResult(0, '', $resultData); 142 | } 143 | 144 | 145 | /** 146 | * @param $uploadedFile UploadedFile 147 | * @param string $filename 148 | * @return bool 149 | * @throws \yii\base\Exception 150 | */ 151 | protected function saveFile(UploadedFile $uploadedFile, string $filename) 152 | { 153 | $filename = $this->getFullFilename($filename, $this->savePath); 154 | if ($this->saveFileCallback && is_callable($this->saveFileCallback)) { 155 | return call_user_func($this->saveFileCallback, $filename, $uploadedFile, $this); 156 | } 157 | FileHelper::createDirectory(dirname($filename)); 158 | return $uploadedFile->saveAs($filename); 159 | } 160 | 161 | 162 | /** 163 | * @param $uploadedFile UploadedFile 164 | * @return string 165 | * @throws \Exception 166 | */ 167 | private function getFileName($uploadedFile) 168 | { 169 | if ($this->fileSaveNameCallback && is_callable($this->fileSaveNameCallback)) { 170 | $filename = call_user_func($this->fileSaveNameCallback, $uploadedFile, $this); 171 | } else { 172 | $filename = md5(microtime() . random_int(10000, 99999)); 173 | } 174 | if (strpos($filename, '.') === false) { 175 | $filename .= '.' . $uploadedFile->getExtension(); 176 | } 177 | return $filename; 178 | } 179 | 180 | /** 181 | * @param $filename 182 | * @param $path 183 | * @return bool|string 184 | */ 185 | protected function getFullFilename($filename, $path) 186 | { 187 | $filename = Yii::getAlias(rtrim($path, '/') . '/' . $filename); 188 | if ($this->normalizePath) { 189 | return FileHelper::normalizePath($filename); 190 | } 191 | return $filename; 192 | } 193 | 194 | /** 195 | * Parse result 196 | * @param int $code 197 | * @param string $message 198 | * @param array $data 199 | * @return array 200 | */ 201 | protected function parseResult(int $code, $message = '', $data = []) 202 | { 203 | if ($this->returnCallback && is_callable($this->returnCallback)) { 204 | return call_user_func($this->returnCallback, $code, $message, $data); 205 | } 206 | return ['code' => $code, 'massage' => $message, 'data' => $data]; 207 | } 208 | 209 | /** 210 | * @param string $fileWebName 211 | * @throws InvalidConfigException 212 | */ 213 | protected function deleteFile(string $fileWebName) 214 | { 215 | $fileWebNames = explode('/', $fileWebName); 216 | $this->deleteLocalFile(end($fileWebNames)); 217 | $oss = \Yii::$app->get('oss'); 218 | $oss->delete(ltrim($fileWebName, '/')); 219 | } 220 | 221 | /** 222 | * @param string $filename 223 | */ 224 | protected function deleteLocalFile(string $filename) 225 | { 226 | $fileAbsoluteName = $this->getFullFilename($filename, $this->savePath); 227 | @unlink($fileAbsoluteName); 228 | } 229 | 230 | /** 231 | * @param string $filename 232 | * @throws InvalidConfigException 233 | */ 234 | public function uploadOSS(string $filename) 235 | { 236 | $fileAbsoluteName = $this->getFullFilename($filename, $this->savePath); 237 | $fileWebName = $this->getFullFilename($filename, $this->webPath); 238 | $oss = \Yii::$app->get('oss'); 239 | $oss->upload(ltrim($fileWebName, '/'), $fileAbsoluteName); 240 | } 241 | } -------------------------------------------------------------------------------- /OSS.php: -------------------------------------------------------------------------------- 1 | 4 | * createTime : 2016/3/16 18:56 5 | * description: 6 | */ 7 | 8 | namespace yiier\AliyunOSS; 9 | 10 | use OSS\OssClient; 11 | use yii\base\Component; 12 | use yii\base\InvalidConfigException; 13 | 14 | class OSS extends Component 15 | { 16 | /** 17 | * @var string 阿里云OSS AccessKeyID 18 | */ 19 | public $accessKeyId; 20 | 21 | /** 22 | * @var string 阿里云OSS AccessKeySecret 23 | */ 24 | public $accessKeySecret; 25 | 26 | /** 27 | * @var string 阿里云的bucket空间 28 | */ 29 | public $bucket; 30 | 31 | /** 32 | * @var string OSS内网地址, 如:oss-cn-hangzhou-internal.aliyuncs.com 33 | */ 34 | public $lanDomain; 35 | 36 | /** 37 | * @var string OSS外网地址, 如:oss-cn-hangzhou.aliyuncs.com 38 | */ 39 | public $wanDomain; 40 | 41 | /** 42 | * @var OssClient 43 | */ 44 | private $_ossClient; 45 | 46 | /** 47 | * 从lanDomain和wanDomain中选取, 默认走外网 48 | * @var string 最终操作域名 49 | */ 50 | protected $baseUrl; 51 | 52 | /** 53 | * @var bool 是否私有空间, 默认公开空间 54 | */ 55 | public $isPrivate = false; 56 | 57 | /** 58 | * @var bool 上传文件是否使用内网,免流量费 59 | */ 60 | public $isInternal = false; 61 | 62 | public function init() 63 | { 64 | if ($this->accessKeyId === null) { 65 | throw new InvalidConfigException('The "accessKeyId" property must be set.'); 66 | } elseif ($this->accessKeySecret === null) { 67 | throw new InvalidConfigException('The "accessKeySecret" property must be set.'); 68 | } elseif ($this->bucket === null) { 69 | throw new InvalidConfigException('The "bucket" property must be set.'); 70 | } elseif ($this->lanDomain === null) { 71 | throw new InvalidConfigException('The "lanDomain" property must be set.'); 72 | } elseif ($this->wanDomain === null) { 73 | throw new InvalidConfigException('The "wanDomain" property must be set.'); 74 | } 75 | 76 | $this->baseUrl = $this->isInternal ? $this->lanDomain : $this->wanDomain; 77 | } 78 | 79 | /** 80 | * @return \OSS\OssClient 81 | * @throws \OSS\Core\OssException 82 | */ 83 | public function getClient() 84 | { 85 | if ($this->_ossClient === null) { 86 | $this->setClient(new OssClient($this->accessKeyId, $this->accessKeySecret, $this->baseUrl)); 87 | } 88 | return $this->_ossClient; 89 | } 90 | 91 | /** 92 | * @param \OSS\OssClient $ossClient 93 | */ 94 | public function setClient(OssClient $ossClient) 95 | { 96 | $this->_ossClient = $ossClient; 97 | } 98 | 99 | /** 100 | * @param $path 101 | * @return bool 102 | * @throws \OSS\Core\OssException 103 | */ 104 | public function has($path) 105 | { 106 | return $this->getClient()->doesObjectExist($this->bucket, $path); 107 | } 108 | 109 | /** 110 | * @param $path 111 | * @return bool 112 | * @throws \OSS\Core\OssException 113 | */ 114 | public function read($path) 115 | { 116 | if (!($resource = $this->readStream($path))) { 117 | return false; 118 | } 119 | $resource['contents'] = stream_get_contents($resource['stream']); 120 | fclose($resource['stream']); 121 | unset($resource['stream']); 122 | return $resource; 123 | } 124 | 125 | /** 126 | * @param $path 127 | * @return array|bool 128 | * @throws \OSS\Core\OssException 129 | */ 130 | public function readStream($path) 131 | { 132 | $url = $this->getClient()->signUrl($this->bucket, $path, 3600); 133 | $stream = fopen($url, 'r'); 134 | if (!$stream) { 135 | return false; 136 | } 137 | return compact('stream', 'path'); 138 | } 139 | 140 | /** 141 | * @param $fileName string 文件名 eg: '824edb4e295892aedb8c49e4706606d6.png' 142 | * @param $filePath string 要上传的文件绝对路径 eg: '/storage/image/824edb4e295892aedb8c49e4706606d6.png' 143 | * @return null 144 | * @throws \OSS\Core\OssException 145 | */ 146 | public function upload($fileName, $filePath) 147 | { 148 | return $this->getClient()->uploadFile($this->bucket, $fileName, $filePath); 149 | } 150 | 151 | /** 152 | * 删除文件 153 | * @param $path 154 | * @return bool 155 | * @throws \OSS\Core\OssException 156 | */ 157 | public function delete($path) 158 | { 159 | return $this->getClient()->deleteObject($this->bucket, $path) === null; 160 | } 161 | 162 | /** 163 | * 创建文件夹 164 | * @param $dirName 165 | * @return array|bool 166 | * @throws \OSS\Core\OssException 167 | */ 168 | public function createDir($dirName) 169 | { 170 | $result = $this->getClient()->createObjectDir($this->bucket, rtrim($dirName, '/')); 171 | if ($result !== null) { 172 | return false; 173 | } 174 | return ['path' => $dirName]; 175 | } 176 | 177 | /** 178 | * 获取 Bucket 中所有文件的文件名,返回 Array。 179 | * @param array $options = [ 180 | * 'max-keys' => max-keys用于限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000。 181 | * 'prefix' => 限定返回的object key必须以prefix作为前缀。注意使用prefix查询时,返回的key中仍会包含prefix。 182 | * 'delimiter' => 是一个用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素 183 | * 'marker' => 用户设定结果从marker之后按字母排序的第一个开始返回。 184 | * ] 185 | * @return array 186 | * @throws \OSS\Core\OssException 187 | */ 188 | public function getAllObject($options = []) 189 | { 190 | $objectListing = $this->getClient()->listObjects($this->bucket, $options); 191 | $objectKeys = []; 192 | foreach ($objectListing->getObjectList() as $objectSummary) { 193 | $objectKeys[] = $objectSummary->getKey(); 194 | } 195 | return $objectKeys; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Aliyun OSS 2 | =============== 3 | Yii2 阿里云 OSS 4 | 5 | [![Latest Stable Version](https://poser.pugx.org/yiier/yii2-aliyun-oss/v/stable)](https://packagist.org/packages/yiier/yii2-aliyun-oss) 6 | [![Total Downloads](https://poser.pugx.org/yiier/yii2-aliyun-oss/downloads)](https://packagist.org/packages/yiier/yii2-aliyun-oss) 7 | [![Latest Unstable Version](https://poser.pugx.org/yiier/yii2-aliyun-oss/v/unstable)](https://packagist.org/packages/yiier/yii2-aliyun-oss) 8 | [![License](https://poser.pugx.org/yiier/yii2-aliyun-oss/license)](https://packagist.org/packages/yiier/yii2-aliyun-oss) 9 | 10 | Installation 11 | ------------ 12 | 13 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 14 | 15 | Either run 16 | 17 | ``` 18 | php composer.phar require --prefer-dist yiier/yii2-aliyun-oss "*" 19 | ``` 20 | 21 | or add 22 | 23 | ``` 24 | "yiier/yii2-aliyun-oss": "*" 25 | ``` 26 | 27 | to the require section of your `composer.json` file. 28 | 29 | 30 | Usage 31 | ----- 32 | 33 | 配置文件添加组件 : 34 | 35 | ```php 36 | components => [ 37 | 'oss' => [ 38 | 'class' => 'yiier\AliyunOSS\OSS', 39 | 'accessKeyId' => 'xxxxx', // 阿里云OSS AccessKeyID 40 | 'accessKeySecret' => 'xxxx', // 阿里云OSS AccessKeySecret 41 | 'bucket' => 'xxx', // 阿里云的bucket空间 42 | 'lanDomain' => 'oss-cn-hangzhou-internal.aliyuncs.com', // OSS内网地址 43 | 'wanDomain' => 'oss-cn-hangzhou.aliyuncs.com', //OSS外网地址 44 | 'isInternal' => true // 上传文件是否使用内网,免流量费(选填,默认 false 是外网) 45 | ], 46 | ] 47 | ``` 48 | 49 | ```php 50 | /** @var \yiier\AliyunOSS\OSS $oss */ 51 | $oss = \Yii::$app->get('oss'); 52 | $fh = '/vagrant/php/baseapi/web/storage/image/824edb4e295892aedb8c49e4706606d6.png'; 53 | $oss->upload('824edb4e295892aedb8c49e4706606d6.png', $fh); 54 | 55 | 或者 56 | 57 | $oss->upload('storage/image/824edb4e295892aedb8c49e4706606d6.png', $fh); // 会自动创建文件夹 58 | 59 | 其他用法 60 | 61 | $oss->createDir('storage/image/'); //创建文件夹 62 | $oss->delete('824edb4e295892aedb8c49e4706606d6.png'); // 删除文件 63 | $oss->delete('storage/image/824edb4e295892aedb8c49e4706606d6.png'); // 删除文件,如果这个文件是此文件夹的最后一个文件,则会把文件夹一起删除 64 | $oss->delete('storage/image/'); // 删除文件夹,但是要确保是空文件夹 65 | $oss->getAllObject(); // 获取根目录下的所有文件名,默认是100个 66 | $oss->getAllObject(['prefix' => 'storage/image/']); // 获取 `storage/image/` 目录下的所有文件名,默认是100个 67 | ``` 68 | 69 | 70 | ### Upload action 71 | 72 | 73 | 视图文件: 74 | 75 | ```php 76 | ['enctype' => 'multipart/form-data']]); ?> 77 | field($model, 'images[]')->fileInput() ?> 78 |
79 | 'btn btn-primary']) ?> 80 |
81 | 82 | ``` 83 | 84 | 控制器: 85 | 86 | ```php 87 | namespace admin\controllers; 88 | 89 | use yii\web\Controller; 90 | use yiier\AliyunOSS\FileUploadAction; 91 | 92 | class FileController extends Controller 93 | { 94 | public function actions() 95 | { 96 | return [ 97 | 'upload-images' => [ 98 | 'class' => FileUploadAction::class, 99 | 'fileParam' => 'images', 100 | 'keepLocalFile' => true, // default false 101 | 'savePath' => '@webroot/uploads', 102 | 'webPath' => '@web/uploads', 103 | ], 104 | ]; 105 | } 106 | } 107 | ``` 108 | 109 | PS:请求的参数 `images` 值是个数组 -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiier/yii2-aliyun-oss", 3 | "description": "Yii2 阿里云 OSS", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","AliyunOSS","OSS"], 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "forecho", 10 | "email": "caizhenghai@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "*", 15 | "aliyuncs/oss-sdk-php": ">2.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "yiier\\AliyunOSS\\": "" 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------