UEditor上传图片、视频等,默认是传到本地,而实际开发过程中,往往需要把附件保存至单独的文件服务器,而使用阿里云OSS来存储图片等附件的越来越多。本文主要介绍在Yii框架中,通过把UEditor改造成widget的方式,将上传的附件保存至OSS。

UEditor简介

UEditor是由百度WEB前端研发部开发的所见即所得的开源富文本编辑器,具有轻量、可定制、用户体验优秀等特点。开源基于BSD协议,所有源代码在协议允许范围内可自由修改和使用。

在附件上传方面,UEditor在前端渲染完成后,会根据serverUrl配置项(可自定义)请求后端配置(主要是文件配置),请求成功后,接下来附件上传操作都会通过请求serverUrl来实现,并在上传成功后,根据已请求到的后端配置进行本地处理(比如加上Url前缀)。


开始整合

1.下载UEditor源码,并调整目录结构

 (1)下载源码,本文中以 [1.4.3.3 PHP 版本] UTF8 为例

 (2)打开yii根目录,创建 common/widgets/ueditor/assets 目录,把UEditor源码拷贝到该目录下

 (3)把 common/widgets/ueditor/assets/php 目录下的文件都拷贝到 common/widgets/ueditor/ 目录下

 (4)在 common/widgets/ueditor/ 创建 config_oss.json、 UEditor.php、 UEditorAction.php、 UEditorAsset.php 这四个文件

2.在view中正常调用小部件

 (1)创建Assets管理器,编辑UEditorAsset.php文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    <?php
	namespace common\widgets\ueditor;

	use yii\web\AssetBundle;

	class UEditorAsset extends AssetBundle
	{

	    public $js = [
	        'ueditor.config.js',
	        'ueditor.all.js',
	    ];
	   
	    public function init()
	    {
	        $this->sourcePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'assets';
	    }
	}
    ?>

 (2)创建Widget,编辑UEditor.php文件(注意init方法中serverUrl的设置),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    <?php
	namespace common\widgets\ueditor;

	use Yii;
	use yii\helpers\ArrayHelper;
	use yii\helpers\Html;
	use yii\helpers\Json;
	use yii\helpers\Url;
	use yii\web\View;
	use yii\widgets\InputWidget;

	class UEditor extends InputWidget
	{
	    //配置选项,参阅Ueditor官网文档(定制菜单等)
	    public $clientOptions = [];

	    //默认配置
	    protected $_options;

	    /**
	     * @throws \yii\base\InvalidConfigException
	     */
	    public function init()
	    {
	        if (isset($this->options['id'])) {
	            $this->id = $this->options['id'];
	        } else {
	            $this->id = $this->hasModel() ? Html::getInputId($this->model,
	                $this->attribute) : $this->id;
	        }
	        $this->_options = [
	            'serverUrl' => Url::to(['upload']),	 //与后端交互的接口地址
	            'initialFrameWidth' => '100%',
	            'initialFrameHeight' => '400',
	            'lang' => (strtolower(Yii::$app->language) == 'en-us') ? 'en' : 'zh-cn',
	        ];
	        $this->clientOptions = ArrayHelper::merge($this->_options, $this->clientOptions);
	        parent::init();
	    }

	    public function run()
	    {
	        $this->registerClientScript();
	        if ($this->hasModel()) {
	            return Html::activeTextarea($this->model, $this->attribute, ['id' => $this->id]);
	        } else {
	            return Html::textarea($this->id, $this->value, ['id' => $this->id]);
	        }
	    }

	    /**
	     * 注册客户端脚本
	     */
	    protected function registerClientScript()
	    {
	        UEditorAsset::register($this->view);
	        $clientOptions = Json::encode($this->clientOptions);
	        $script = "UE.getEditor('" . $this->id . "', " . $clientOptions . ");";
	        $this->view->registerJs($script, View::POS_READY);
	    }
	}
    ?>

 (3)在视图文件中,使用以下代码调用UEditor:

1
	<?= $form->field($model,'content')->widget('common\widgets\ueditor\UEditor',[]) ?>

3.配置并实现上传

 (1)上一步中,在UEditor.php文件中配置了serverUrl,前端渲染完成后,会通过请求 serverUrl??action=config&&noCache=1532939514330 的方式获取后端配置(主要是上传方面),因此除了原有的config.json,再增加一个config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	/* OSS相关的配置,注释只允许使用多行方式 */
	{
	    "oss_open" : true,
	    "oss_config" : {
	        "accessKeyId" : "accessKeyId_val",
	        "accessKeySecret" : "accessKeySecret_val",
	        "bucket" : "bucket_val",
	        "endPoint" : "oss-cn-beijing.aliyuncs.com"
	    },
	    "imageUrlPrefix" : "http://img.com",
	    "videoUrlPrefix" : "http://haohao-cn.oss-cn-beijing.aliyuncs.com",
	    "fileUrlPrefix" : "http://haohao-cn.oss-cn-beijing.aliyuncs.com",
	    "scrawlUrlPrefix" : "http://img.com"
	}

 (2)UEditor请求配置及上传操作需要请求serverUrl,而这一步需要在控制器(这里是WikiController.php)中设置一个 独立操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    <?php
	/**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'upload' => [
                'class' => 'common\widgets\ueditor\UEditorAction',
                'config' => [
                    // "imageUrlPrefix"  => "http://www.baidu.com",//图片访问路径前缀
                    // "imagePathFormat" => "/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}" //上传保存路径
                    // "imageRoot" => Yii::getAlias("@webroot"),
                    // "imageActionName" => Url::toRoute(['/attach/upload']),
                ],
            ]
        ];
    }
    ?>

 (2)修改UEditorAction.php文件,根据controller.php修改,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    <?php
	namespace common\widgets\ueditor;

	//header('Access-Control-Allow-Origin: http://www.baidu.com'); //设置http://www.baidu.com允许跨域访问
	//header('Access-Control-Allow-Headers: X-Requested-With,X_Requested_With'); //设置允许的跨域header
	// date_default_timezone_set("Asia/chongqing");
	// error_reporting(E_ERROR);
	// header("Content-Type: text/html; charset=utf-8");

	use Yii;
	use yii\base\Action;
	use yii\helpers\ArrayHelper;

	class UEditorAction extends Action
	{
	    /**
	     * @var array
	     */
	    public $config = [];

	    public function init()
	    {
	        //close csrf
	        Yii::$app->request->enableCsrfValidation = false;  //不关闭的话,上传时会出现400错误

	        parent::init();
	    }

	    public function run()
	    {
	        $CONFIG = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents(__DIR__ . "/config.json")), true);

	        //增加以下三行
	        $CONFIG_oss = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents(__DIR__ . "/config_oss.json")), true);
	        $CONFIG_manual = $this->config; //在 controller的独立动作 中配置的,优先级最高
	        $CONFIG = ArrayHelper::merge($CONFIG, ArrayHelper::merge($CONFIG_oss, $CONFIG_manual));

	        $action = Yii::$app->request->get('action');
	        switch ($action) {
	            case 'config':
	                unset($CONFIG['oss_config']);  //增加这一行,oss配置不能传到前端(安全考虑)
	                $result = json_encode($CONFIG);
	                break;

	            /* 上传图片 */
	            case 'uploadimage':
	            /* 上传涂鸦 */
	            case 'uploadscrawl':
	            /* 上传视频 */
	            case 'uploadvideo':
	            /* 上传文件 */
	            case 'uploadfile':
	                $result = include("action_upload.php");
	                break;

	            /* 列出图片 */
	            case 'listimage':
	                $result = include("action_list.php");
	                break;
	            /* 列出文件 */
	            case 'listfile':
	                $result = include("action_list.php");
	                break;

	            /* 抓取远程文件 */
	            case 'catchimage':
	                $result = include("action_crawler.php");
	                break;

	            default:
	                $result = json_encode(array(
	                    'state'=> '请求地址出错'
	                ));
	                break;
	        }

	        /* 输出结果 */
	        if (isset($_GET["callback"])) {
	            if (preg_match("/^[\w_]+$/", $_GET["callback"])) {
	                echo htmlspecialchars($_GET["callback"]) . '(' . $result . ')';
	            } else {
	                echo json_encode(array(
	                    'state'=> 'callback参数不合法'
	                ));
	            }
	        } else {
	            echo $result;
	        }
	    }
	}
    ?>

 (3)上传文件的方法主要用到action_upload文件,注意uploadscrawl中注释的配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
	<?php
	namespace common\widgets\ueditor;

	/**
	 * 上传附件和上传视频
	 * User: Jinqn
	 * Date: 14-04-09
	 * Time: 上午10:17
	 */
	include "Uploader.class.php";

	/* 上传配置 */
	$base64 = "upload";
	switch (htmlspecialchars($_GET['action'])) {
	    case 'uploadimage':
	        $config = array(
	            "pathFormat" => $CONFIG['imagePathFormat'],
	            "maxSize" => $CONFIG['imageMaxSize'],
	            "allowFiles" => $CONFIG['imageAllowFiles']
	        );
	        $fieldName = $CONFIG['imageFieldName'];
	        break;
	    case 'uploadscrawl':
	        $config = array(
	            "pathFormat" => $CONFIG['scrawlPathFormat'],
	            "maxSize" => $CONFIG['scrawlMaxSize'],
	            // "allowFiles" => $CONFIG['scrawlAllowFiles'],   //这一项配置信息里没有,需要注释掉
	            "oriName" => "scrawl.png"
	        );
	        $fieldName = $CONFIG['scrawlFieldName'];
	        $base64 = "base64";
	        break;
	    case 'uploadvideo':
	        $config = array(
	            "pathFormat" => $CONFIG['videoPathFormat'],
	            "maxSize" => $CONFIG['videoMaxSize'],
	            "allowFiles" => $CONFIG['videoAllowFiles']
	        );
	        $fieldName = $CONFIG['videoFieldName'];
	        break;
	    case 'uploadfile':
	    default:
	        $config = array(
	            "pathFormat" => $CONFIG['filePathFormat'],
	            "maxSize" => $CONFIG['fileMaxSize'],
	            "allowFiles" => $CONFIG['fileAllowFiles']
	        );
	        $fieldName = $CONFIG['fileFieldName'];
	        break;
	}

	//增加这两句,加入oss配置
	$config['oss_open'] = $CONFIG['oss_open'];
	$config['oss_config'] = $CONFIG['oss_config'];

	/* 生成上传实例对象并完成上传 */
	$up = new Uploader($fieldName, $config, $base64);

	/**
	 * 得到上传文件所对应的各个参数,数组结构
	 * array(
	 *     "state" => "",          //上传状态,上传成功时必须返回"SUCCESS"
	 *     "url" => "",            //返回的地址
	 *     "title" => "",          //新文件名
	 *     "original" => "",       //原始文件名
	 *     "type" => ""            //文件类型
	 *     "size" => "",           //文件大小
	 * )
	 */

	/* 返回数据 */
	return json_encode($up->getFileInfo());
    ?>

 (4)上传功能主要在Uploader.class.php中实现,对该文件进行修改:

  • 在最上方加入命名空间(namespace common\widgets\ueditor;),并引入OSS扩展(use OSS\OssClient;)
  • 在构造方法中,加入判断,如果($config[‘oss_open’] == true),则执行上传到OSS的方法
  • 增加两个方法,upFileToOss() 和 upBase64ToOss(),在upFile()和upBase64()基础上,增加上传至OSS的代码(思路:先上传到本地,然后调用OSS的方法上传到OSS,上传成功后,删除本地文件)


完整代码见 ueditor.zip