在 Yii 2.0 中,基于 FileHelper 创建多级目录时,报错:chmod(): Operation not permitted,进而导致每次只能成功创建一级 的分析与解决
1、上传选题素材时,服务器内部错误。如图1
{ "name": "Internal Server Error", "message": "服务器内部错误。", "code": 0, "status": 500 }
2、决定调整为开发模式 (dev),再次上传选题素材,以查看更为详细的错误信息,报错:chmod(): Operation not permitted,如图2
{ "name": "Exception", "message": "Failed to change permissions for directory \"/webtv/wangjie/pcs-api/tmp/2019/12/05\": chmod(): Operation not permitted", "code": 2, "type": "yii\\base\\Exception", "file": "/mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php", "line": 635, "stack-trace": [ "#0 /mcloud/www/pcs-api/common/services/AssetService.php(77): yii\\helpers\\BaseFileHelper::createDirectory('/webtv/wangjie/...')", "#1 /mcloud/www/pcs-api/api/rests/asset/UploadAction.php(93): common\\services\\AssetService::uploadTempAssets(Array)", "#2 [internal function]: api\\rests\\asset\\UploadAction->run()", "#3 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Action.php(94): call_user_func_array(Array, Array)", "#4 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Controller.php(157): yii\\base\\Action->runWithParams(Array)", "#5 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Module.php(528): yii\\base\\Controller->runAction('upload', Array)", "#6 /mcloud/www/pcs-api/vendor/yiisoft/yii2/web/Application.php(103): yii\\base\\Module->runAction('v1/asset/upload', Array)", "#7 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))", "#8 /mcloud/www/pcs-api/api/web/index.php(17): yii\\base\\Application->run()", "#9 {main}" ], "previous": { "name": "PHP Warning", "message": "chmod(): Operation not permitted", "code": 2, "type": "yii\\base\\ErrorException", "file": "/mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php", "line": 633, "stack-trace": [ "#0 [internal function]: yii\\base\\ErrorHandler->handleError(2, 'chmod(): Operat...', '/mcloud/www/pcs...', 633, Array)", "#1 /mcloud/www/pcs-api/vendor/yiisoft/yii2/helpers/BaseFileHelper.php(633): chmod('/webtv/wangjie/...', 509)", "#2 /mcloud/www/pcs-api/common/services/AssetService.php(77): yii\\helpers\\BaseFileHelper::createDirectory('/webtv/wangjie/...')", "#3 /mcloud/www/pcs-api/api/rests/asset/UploadAction.php(93): common\\services\\AssetService::uploadTempAssets(Array)", "#4 [internal function]: api\\rests\\asset\\UploadAction->run()", "#5 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Action.php(94): call_user_func_array(Array, Array)", "#6 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Controller.php(157): yii\\base\\Action->runWithParams(Array)", "#7 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Module.php(528): yii\\base\\Controller->runAction('upload', Array)", "#8 /mcloud/www/pcs-api/vendor/yiisoft/yii2/web/Application.php(103): yii\\base\\Module->runAction('v1/asset/upload', Array)", "#9 /mcloud/www/pcs-api/vendor/yiisoft/yii2/base/Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))", "#10 /mcloud/www/pcs-api/api/web/index.php(17): yii\\base\\Application->run()", "#11 {main}" ] } }
3、查看挂载的目录,/webtv/wangjie/pcs-api/tmp/2019/12/05,连接上传了 2 次,第 1 次生成了目录:12,第 2 次生成了目录:05,如图3
[root@d48dedd9fceb pcs-api]# ls 2019 tmp [root@d48dedd9fceb pcs-api]# cd tmp [root@d48dedd9fceb tmp]# ls 2019 [root@d48dedd9fceb tmp]# cd 2019 [root@d48dedd9fceb 2019]# ls 10 11 12 [root@d48dedd9fceb 2019]# cd 12 [root@d48dedd9fceb 12]# ls 05 [root@d48dedd9fceb 12]# cd 05 [root@d48dedd9fceb 05]# ls [root@d48dedd9fceb 05]# pwd /webtv/wangjie/pcs-api/tmp/2019/12/05 [root@d48dedd9fceb 05]# ls -l total 0 [root@d48dedd9fceb 05]# cd .. [root@d48dedd9fceb 12]# ls -l total 0 drwxrwxrwx 2 root root 0 Dec 5 15:28 05 [root@d48dedd9fceb 12]#
4、分析原因,当前用户 为 nginx ,在开发环境中查看目录 的用户,而在报错的环境中,目录的用户为 root,且模式为 0777,当前用户指的是执行 PHP 的用户。很可能和通常的 shell 或者 FTP 用户不是同一个。在大多数系统下文件模式只能被文件所有者的用户改变。如图4
[root@685b4b7fd197 /]# cd /webtv/wangjiedev/pcs-api/ [root@685b4b7fd197 pcs-api]# ls 2019 php-fpm1.conf php-fpm.conf tmp [root@685b4b7fd197 pcs-api]# cd tmp [root@685b4b7fd197 tmp]# ls -l total 0 drwxrwxr-x 9 nginx nginx 101 Dec 2 16:26 2019
5、最终决定继续类:yii\helpers\FileHelper,覆写静态方法:createDirectory($path, $mode = 0775, $recursive = true),当 chmod() 改变文件模式失败后,返回 true,不抛出异常,继续后续的执行,新建类:\common\helpers\FileHelper.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2019/12/05 * Time: 16:45 */ namespace common\helpers; /** * File system helper. * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class FileHelper extends \yii\helpers\FileHelper { /** * Creates a new directory. * * This method is similar to the PHP `mkdir()` function except that * it uses `chmod()` to set the permission of the created directory * in order to avoid the impact of the `umask` setting. * * @param string $path path of the directory to be created. * @param int $mode the permission to be set for the created directory. * @param bool $recursive whether to create parent directories if they do not exist. * @return bool whether the directory is created successfully * @throws \yii\base\Exception if the directory could not be created (i.e. php error due to parallel changes) */ public static function createDirectory($path, $mode = 0775, $recursive = true) { if (is_dir($path)) { return true; } $parentDir = dirname($path); // recurse if parent dir does not exist and we are not at the root of the file system. if ($recursive && !is_dir($parentDir) && $parentDir !== $path) { static::createDirectory($parentDir, $mode, true); } try { if (!mkdir($path, $mode)) { return false; } } catch (\Exception $e) { if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288 throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); } } try { return @chmod($path, $mode); } catch (\Exception $e) { throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); } } }
6、后续创建目录时,皆引用新创建的类:common\helpers\FileHelper,进入报错的环境,手动删除目录:/webtv/wangjie/pcs-api/tmp/2019/12,如图5
7、再次上传选题素材,报错:”创建目录:/webtv/wangjie/pcs-api/tmp/2019/12/05,失败”,如图6
{ "name": "Internal Server Error", "message": "创建目录:/webtv/wangjie/pcs-api/tmp/2019/12/05,失败", "code": 202002, "status": 500, "type": "yii\\web\\ServerErrorHttpException" }
8、查看挂载的目录,/webtv/wangjie/pcs-api/tmp/2019/12/05,已经第 1 次就创建成功,已经确定不能够一次性创建多个目录的问题已经得到解决,如图7
9、分析原因在于,静态方法:createDirectory($path, $mode = 0775, $recursive = true),未返回 true,进而导致判断时,以为创建目录失败,抛出异常,因此,不论 chmod 结果如何,皆返回 true
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2019/12/05 * Time: 16:45 */ namespace common\helpers; /** * File system helper. * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class FileHelper extends \yii\helpers\FileHelper { /** * Creates a new directory. * * This method is similar to the PHP `mkdir()` function except that * it uses `chmod()` to set the permission of the created directory * in order to avoid the impact of the `umask` setting. * * @param string $path path of the directory to be created. * @param int $mode the permission to be set for the created directory. * @param bool $recursive whether to create parent directories if they do not exist. * @return bool whether the directory is created successfully * @throws \yii\base\Exception if the directory could not be created (i.e. php error due to parallel changes) */ public static function createDirectory($path, $mode = 0775, $recursive = true) { if (is_dir($path)) { return true; } $parentDir = dirname($path); // recurse if parent dir does not exist and we are not at the root of the file system. if ($recursive && !is_dir($parentDir) && $parentDir !== $path) { static::createDirectory($parentDir, $mode, true); } try { if (!mkdir($path, $mode)) { return false; } } catch (\Exception $e) { if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288 throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); } } try { // return chmod($path, $mode); @chmod($path, $mode); return true; } catch (\Exception $e) { throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e); } } }
10、手动删除目录:/webtv/wangjie/pcs-api/tmp/2019/12,再次上传选题素材,上传资源成功,符合预期,如图8
11、查看挂载的目录,/webtv/wangjie/pcs-api/tmp/2019/12/05/1575537845.5862.1232301337.jpg 已经存在,符合预期,如图9
近期评论