在 Yii 2.0 中上传文件的接口,在多场景(每个场景的验证规则有差异)下复用的实现

1、上传文件的接口,之有仅实现了 上传选题素材 的场景,现在需要新增 上传基地图标 的场景,决定复用上传文件的接口,如图1

图1

2、新增请求参数,scenario:可选,场景,default:默认;config_base_location_icon:基地设置的图标,默认:default

3、编辑 \api\rests\asset\UploadAction.php,场景通过构造初始化配置来设置

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\asset;

use Yii;
use api\models\Upload;
use api\services\AssetService;
use yii\base\Exception;
use yii\base\Model;
use yii\web\UnprocessableEntityHttpException;
use yii\web\UploadedFile;
use yii\web\ServerErrorHttpException;

/**
 * 上传资源:/assets/upload(asset/upload)
 *
 * 1、请求参数列表
 * (1)files[]:必填,上传的文件
 * (2)scenario:可选,场景,default:默认;config_base_location_icon:基地设置的图标,默认:default
 *
 * 2、输入数据验证规则
 * (1)必填:files[]
 * (2)文件
 * A、可接受上传的文件扩展名列表
 * B、可接受上传的 MIME 类型列表
 * C、上传文件所需最少多少 Byte 的大小
 * D、上传文件所需最多多少 Byte 的大小
 * E、给定属性最多能承载多少个文件
 * (3)范围(['default', 'config_base_location_icon']):scenario
 *
 * 3、操作数据
 * (1)上传文件至临时的资源目录
 *
 * UploadAction implements the API endpoint for creating a new model from the given data.
 *
 * For more details and usage information on UploadAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class UploadAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */    public $viewAction = 'view';

    /**
     * Uploads a new model.
     * @return array the model newly uploaded
     * @throws ServerErrorHttpException 如果创建目录失败,将抛出 500 HTTP 异常
     * @throws ServerErrorHttpException if there is any error when creating the model
     * @throws Exception if the directory could not be created (i.e. php error due to parallel changes)
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        $request = Yii::$app->request;
        $scenario = $request->post('scenario', $this->scenario);

        /* 验证场景的范围(['default', 'config_base_location_icon']) */        if (!in_array($scenario, [$this->scenario, Upload::SCENARIO_CONFIG_BASE_LOCATION_ICON])) {
            throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '226076'), ['scenario' => $scenario])), 226076);
        }

        /* @var $model Upload */        $model = new Upload(['scenario' => $scenario]);

        // 将 UploadedFile 实例数组赋值给 Upload::files
        $model->files = UploadedFile::getInstances($model, 'files');

        if ($model->validate()) {
            $assetService = new AssetService();

            $result = $assetService->uploadTempAssets($model->files);

            return ['code' => 10000, 'message' => Yii::t('success', '126033'), 'data' => ['items' => $result]];

        } elseif ($model->hasErrors()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(422, 'Data Validation Failed.');
            $firstError = '';
            foreach ($model->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['code' => 226004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '226004'), ['first_error' => $firstError]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to upload the object for unknown reason.');
        }
    }
}

4、编辑 \common\logics\Upload.php,通过指定规则的 on 属性,一条规则可以只在某个 场景 下应用,以区分不同场景下的验证规则。例:上传选题素材为默认的场景,上传基地图标为基地设置的图标的场景。

<?php

namespace common\logics;

use Yii;
use yii\base\Model;
use yii\web\UploadedFile;

class Upload extends Model
{
    const SCENARIO_CONFIG_BASE_LOCATION_ICON = 'config_base_location_icon'; // 场景:基地设置的图标

    /**
     * @var UploadedFile
     */    public $files;

    /**
     * {@inheritdoc}
     */    public function scenarios()
    {
        $scenarios = parent::scenarios();

        return $scenarios;
    }

    public function rules()
    {
        return [
            [['files'], 'image', 'skipOnEmpty' => false, 'extensions' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['extensions'], 'mimeTypes' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['mimeTypes'], 'minSize' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['minSize'], 'maxSize' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['maxSize'], 'maxFiles' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['maxFiles'], 'minWidth' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['minWidth'], 'maxWidth' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['maxWidth'], 'minHeight' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['minHeight'], 'maxHeight' => Yii::$app->params['pcsApi']['asset']['upload']['scenario']['configBaseLocationIcon']['maxHeight'], 'on' => self::SCENARIO_CONFIG_BASE_LOCATION_ICON],
            [['files'], 'file', 'skipOnEmpty' => false, 'extensions' => Yii::$app->params['pcsApi']['asset']['upload']['extensions'], 'mimeTypes' => Yii::$app->params['pcsApi']['asset']['upload']['mimeTypes'], 'minSize' => Yii::$app->params['pcsApi']['asset']['upload']['minSize'], 'maxSize' => Yii::$app->params['pcsApi']['asset']['upload']['maxSize'], 'maxFiles' => Yii::$app->params['pcsApi']['asset']['upload']['maxFiles']],
        ];
    }

    public function formName()
    {
        return '';
    }
}

5、验证规则的具体参数配置在文件 \common\config\params-local.php

    // 策划指挥系统接口
    'pcsApi' => [
        'asset' => [ // 资源
            'basePath' => 'E:/wwwroot/pcs-api/storage', // BASE PATH
            'tempDir' => '/tmp', // TEMP DIR
            'hostInfo' => 'http://127.0.0.1/pcs-api/storage', // HOME URL
            'baseUrl' => '', // BASE URL
            'upload' => [ // 上传
                'extensions' => 'ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv', // 可接受上传的文件扩展名列表
                'mimeTypes' => 'application/ogg, application/pdf, application/xml, application/zip, application/gzip, audio/mp4, audio/mpeg, audio/ogg, audio/vnd.wave, audio/webm, image/gif, image/jpeg, image/png, image/webp, image/svg+xml, image/tiff, text/css, text/csv, text/plain, text/vcard, text/xml, video/mpeg, video/mp4, video/ogg, video/quicktime, video/webm, video/x-matroska, video/x-ms-wmv, video/x-flv', // 可接受上传的 MIME 类型列表
                'minSize' => null, // 上传文件所需最少多少 Byte 的大小
                'maxSize' => 1024 * 1024 * 1024, // 上传文件所需最多多少 Byte 的大小
                'maxFiles' => 10, // 给定属性最多能承载多少个文件
                'scenario' => [ // 场景
                    'configBaseLocationIcon' => [ // 基地设置的图标
                        'extensions' => 'jpeg, jpg, png', // 可接受上传的文件扩展名列表
                        'mimeTypes' => 'image/jpeg, image/png', // 可接受上传的 MIME 类型列表
                        'minSize' => null, // 上传文件所需最少多少 Byte 的大小
                        'maxSize' => 1024 * 1024 * 2, // 上传文件所需最多多少 Byte 的大小
                        'maxFiles' => 2, // 给定属性最多能承载多少个文件
                        'minWidth' => 50, // 图片的最小宽度
                        'maxWidth' => 200, // 图片的最大宽度
                        'minHeight' => 50, // 图片的最小高度
                        'maxHeight' => 200, // 图片的最大高度
                    ],
                ],
            ],
        ],
    ],

6、上传选题素材 的场景,文件扩展名为 zip 的文件,上传成功,符合预期,如图2

图2

7、基地设置的图标 的场景,文件扩展名为 zip 的文件,上传失败,符合预期,如图3

图3

8、maxFiles:给定属性最多能承载多少个文件。 默认为 1,代表只允许单文件上传。 若值大于一,那么输入值必须为包含最多 maxFiles 个上传文件元素的数组。基地设置的图标 的场景,上传文件的数量应该只能够为 1,现在 maxFiles:2,即可最多上传 2 个文件,原因在于 maxFiles:1 时,响应失败,如图4

图4

9、在不同场景中,一些场景中文件数量只能够为 1 个,一些场景中文件数量可以为多个,现在 maxFiles:2,即可最多上传 2 个文件,如图5

图5

10、编辑 \api\rests\asset\UploadAction.php,判断场景,如果为:config_base_location_icon,则上传单个文件(即第 1 个文件),现在 maxFiles:2,即可最多上传 2 个文件(2 个文件皆会验证,但仅会上传第 1 个文件),符合预期(其实此处最佳的方案应该是响应失败,提示仅能够上传一个文件的,但是却是需要再实现一个上传的接口,有所冗余,因此,最终决定,还是复用),如图6

图6

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\asset;

use Yii;
use api\models\Upload;
use api\services\AssetService;
use yii\base\Exception;
use yii\base\Model;
use yii\web\UnprocessableEntityHttpException;
use yii\web\UploadedFile;
use yii\web\ServerErrorHttpException;

/**
 * 上传资源:/assets/upload(asset/upload)
 *
 * 1、请求参数列表
 * (1)files[]:必填,上传的文件
 * (2)scenario:可选,场景,default:默认;config_base_location_icon:基地设置的图标,默认:default
 *
 * 2、输入数据验证规则
 * (1)必填:files[]
 * (2)文件
 * A、可接受上传的文件扩展名列表
 * B、可接受上传的 MIME 类型列表
 * C、上传文件所需最少多少 Byte 的大小
 * D、上传文件所需最多多少 Byte 的大小
 * E、给定属性最多能承载多少个文件
 * (3)范围(['default', 'config_base_location_icon']):scenario
 *
 * 3、操作数据
 * (1)上传文件至临时的资源目录
 *
 * UploadAction implements the API endpoint for creating a new model from the given data.
 *
 * For more details and usage information on UploadAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class UploadAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */    public $viewAction = 'view';

    /**
     * Uploads a new model.
     * @return array the model newly uploaded
     * @throws ServerErrorHttpException 如果创建目录失败,将抛出 500 HTTP 异常
     * @throws ServerErrorHttpException if there is any error when creating the model
     * @throws Exception if the directory could not be created (i.e. php error due to parallel changes)
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        $request = Yii::$app->request;
        $scenario = $request->post('scenario', $this->scenario);

        /* 验证场景的范围(['default', 'config_base_location_icon']) */        if (!in_array($scenario, [$this->scenario, Upload::SCENARIO_CONFIG_BASE_LOCATION_ICON])) {
            throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '226076'), ['scenario' => $scenario])), 226076);
        }

        /* @var $model Upload */        $model = new Upload(['scenario' => $scenario]);

        // 将 UploadedFile 实例数组赋值给 Upload::files
        $model->files = UploadedFile::getInstances($model, 'files');

        if ($model->validate()) {
            $assetService = new AssetService();

            /* 判断场景,如果为:config_base_location_icon,则上传单个文件(即第 1 个文件) */            if ($scenario == Upload::SCENARIO_CONFIG_BASE_LOCATION_ICON) {
                // 将 UploadedFile 实例赋值给 Upload::files
                $model->files = [$model->files[0]];
            }

            $result = $assetService->uploadTempAssets($model->files);

            return ['code' => 10000, 'message' => Yii::t('success', '126033'), 'data' => ['items' => $result]];

        } elseif ($model->hasErrors()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(422, 'Data Validation Failed.');
            $firstError = '';
            foreach ($model->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['code' => 226004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '226004'), ['first_error' => $firstError]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to upload the object for unknown reason.');
        }
    }
}

 

永夜