这是我花了很多天的时间才得以真正实现的一组需求。

文章后面有完整Demo的GitHub链接。

一、 需求描述

1. 应用是基于ThinkPHP5开发的;

2. 服务器环境是LNMP,PHP版本是7.2,数据库是MySQL5.6;

3. 由用户(包括管理员)上传的图片一类的媒体文件不能直接上传到应用目录中,而要上传到单独的对象存储服务器上;

4. 需要使用富文本编辑器,编辑器中需要上传的图片也都要保存到对象存储服务器;

5. 可以对已上传的图片进行删改查操作。

二、 方案选型

1. 框架:ThinkPHP 5.0.24(比较常用)

2. 编辑器:ueditor1_4_3_3-utf8-php(停更前的最新版本)

3. 对象存储:七牛云(免费10G空间,官方SDK齐全)

4. 开发环境:windows+WAMPServer

三、 产品设计

本文要做的只是一个demo,其中只包含需求中说明的功能,而其他相关的功能比如登录、权限之类的就不在本文的研究范围内。

对以上需求和方案进行分析,可以总结出本文demo需要实现的具体功能如下:

1. bucket管理(七牛云的存储空间),增删改查。

2. 图片管理,上传,增删改查,上传时将信息保存到数据库中,查询时从数据库读取数据并向云存储空间获取图片。

3. ueditor演示,插入本地图片时将图片上传到云存储中并向数据库插入记录,插入远程图片时从数据库读取数据并向云存储获取图片,查看远程列表时获取缩略图,插入时需要获取大图。

四、 实现过程

说明:本文将这个需求当作一个单独的项目来重现实现过程,而且这个实现过程中会对与本主题无关的但在开发过程中需要用到的内容做忽略处理(比如composer、比如其他前端框架),只关注问题本身。

这个demo使用的前端框架(库或插件)主要包括bootstrap、jquery、adminLte、jquery-lazyload等。

1. 在七牛云上准备开发者账号、存储空间和图片样式:

这一过程在本文中大致省略,在七牛官网上一步一步的操作即可,操作完成后需要记下几个参数:

access_key和secret_key(这里暂时只记录主账号的key,更复杂权限操作本文不深入研究);

存储空间的名称,本例创建两个空间分别名为wandoubaba和wandoubaba_user;

每个空间分别设置各自的图片样式,本例都用同样的样式策略(具体样式根据你的实际情况设置):

缩略图:w150.h150.cut

原图:original

原图水印图:original.water

限制宽度等比缩放:w800.water

限制高度等比缩放:h480.water

此外还要对每个存储空间分别绑定域名,七牛云虽然会默认提供一个域名,但是这个默认的域名只能使用1个月,所以还是自己去绑定一个,需要把每个空间对应的域名单独记录下来。

2. 创建并设计数据库:

mysql中创建数据库,名为tp-ue-qn-db:

CREATE DATABASE `tp-ue-qn-db` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

创建表db_bucket和db_picture:

SETNAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;--------------------------------Table structure for db_bucket------------------------------
DROP TABLE IF EXISTS`db_bucket`;CREATE TABLE`db_bucket`  (`bucket_name`varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'bucket名称',`bucket_domain`varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'bucket对应的domain',`bucket_description`varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '文字描述',`bucket_default`tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '默认,0为否,1为是',`bucket_style_thumb`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '缩略图样式名',`bucket_style_original`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '原图样式名',`bucket_style_water`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '原图打水印样式名',`bucket_style_fixwidth`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '限制宽度样式名',`bucket_style_fixheight`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '限制高度样式名',PRIMARY KEY(`bucket_name`) USING BTREE
) ENGINE= InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT =Dynamic;--------------------------------Table structure for db_picture------------------------------
DROP TABLE IF EXISTS`db_picture`;CREATE TABLE`db_picture`  (`picture_id`varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '图片唯一ID',`picture_key`varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '云存储文件名',`bucket_name`varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储仓库',`picture_name`varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '本机文件描述名',`picture_description`varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '图片描述',`picture_protected`tinyint(4) NULL DEFAULT NULL COMMENT '是否保护,0为不保护,1为保护',`admin_id`varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '上传者管理员ID,后台上传时保存',`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '上传者用户ID,用户上传时保存',`create_time`int(10) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',`update_time`int(10) UNSIGNED NULL DEFAULT NULL COMMENT '编辑时间',PRIMARY KEY(`picture_id`) USING BTREE,INDEX`bucket_name`(`bucket_name`) USING BTREE,CONSTRAINT `db_picture_ibfk_1` FOREIGN KEY (`bucket_name`) REFERENCES `db_bucket` (`bucket_name`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE= InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT =Dynamic;SET FOREIGN_KEY_CHECKS = 1;

其中,在bucket表中直接将bucket_name设置为主键,同时还设置了thumb、original、water、fixwidth、fixheight这五个图片样式名,这是结合七牛云的图片规则而设置的。

3. 本地创建项目目录,配置虚拟主机:

我在本地为项目创建目录…/tp-ue-qn-db/,然后在命令行中进入这个目录,执行composer命令安装thinkphp5.0.24框架到www目录:

composer create-project topthink/think=5.0.* www  --prefer-dist

执行后的结果:

你的执行过程提示可能与我不同,但是执行结果是一样的。

接下来就可以为应用建立虚拟机,创建过程省略,创建结果是将本地的http://localhost.tp-ue-qn-db/指向到本地的…/tp-ue-qn-db/www/public目录,创建好可以试着运行一下,结果应该如下:

4. 引入第三方开发包:

接下来命令行进入www目录,用composer引入七牛云官方提供的php-sdk:

composer require qiniu/php-sdk

5. 引入ueditor插件

官网下载地址:https://ueditor.baidu.com/website/download.html

我下载的是1.4.3.3 PHP 版本中的UTF-8版本,但是我遇到了下载不成功的问题,最后是用我的amazon测试主机中使用wget下载成功,然后再用ftp下载到我本地。

wget https://github.com/fex-team/ueditor/releases/download/v1.4.3.3/ueditor1_4_3_3-utf8-php.zip

下载后把压缩包内容解压到应该目录下面,我的解压路径是:

.../tp-ue-qn-db/www/public/static/lib/ueditor

操作到这里,项目目录结构大概是这样:

6. 做与本例无关的必要操作:

主要包括在thinkphp中配置数据库连接、引入需要的前端框架(库或插件)、做一些thinkphp的视力模板等,这些操作必要但与本例无关,而且每个项目都不一样,所以不做讲解。

7. 创建相关文件,编程:

主要目录结构:

这里只展示核心代码,完整的demo可以到github中去获取。

(1) bucket.html

<divclass="box"><divclass="box-header"><span> <ahref="javascript:;"onclick="modal_show_iframe('添加存储空间','{:url("index/picture/bucket_add")}',90)" class="btn btn-primary"><iclass="fa fa-fw fa-plus-square"></i> 新增数据</a> </span><spanclass="pull-right">共有数据:<strong>{$count}</strong></span></div><divclass="box-body"style="overflow-y: hidden;overflow-x: scroll;"><tableclass="table table-bordered table-strited table-hover text-nowrap"><thead><tr><thscope="col"colspan="8">存储空间</th></tr><tr><th>操作</th><th>名称</th><th>域名</th><th>描述</th><th>默认</th><th>缩略图样式</th><th>原图样式</th><th>原图水印样式</th><th>适应宽度样式</th><th>适应高度样式</th></tr></thead><tbody>{volist name='list' id='vo'}<trtitle="{$vo.bucket_description}"><tdclass="td-manage"><atitle="编辑"href="javascript:;"onclick="modal_show_iframe('编辑存储空间','{:url("index/picture/bucket_edit",["name"=>$vo.bucket_name])}','')"><iclass="fa fa-fw fa-pencil-square-o"></i></a> <atitle="删除"href="javascript:;"onclick="ajax_post_confirm('{:url("index/picture/do_bucket_delete")}',{name:'{$vo.bucket_name}'},'{$vo.bucket_name}','删除');"><iclass="fa fa-fw fa-trash-o"></i></a></td><td><spanclass="name">{$vo.bucket_name}</span></td><td>{$vo.bucket_domain}</td><td>{$vo.bucket_description}</td><td>{$vo.bucket_default}</td><td>{$vo.bucket_style_thumb}</td><td>{$vo.bucket_style_original}</td><td>{$vo.bucket_style_water}</td><td>{$vo.bucket_style_fixwidth}</td><td>{$vo.bucket_style_fixheight}</td></tr>{/volist}</tbody></table></div><divclass="box-footer"><divclass="text-warning text-center">在电脑上操作会更舒服一些。</div></div>
</div>

(2) bucket_add.html

<formmethod="post"class="form-horizontal"><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>空间名称:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="空间名称,与云上的bucket一致"id="bucket_name"name="bucket_name"rangelength="[1,50]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>空间域名:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="空间域名,http://.../形式,以/结尾"id="bucket_domain"name="bucket_domain"rangelength="[4,100]"required></div></div>        <divclass="form-group"><labelclass="control-label col-sm-3">描述:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="文字描述"id="bucket_description"name="bucket_description"maxlength="100"></div></div><divclass="form-group"><labelclass="control-label col-sm-3">默认空间:</label><divclass="col-sm-9"><inputtype="checkbox"id="bucket_default"name="bucket_default" />勾选为默认,只可以有1个默认空间</div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>缩略图样式:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="缩略图样式名"id="bucket_style_thumb"name="bucket_style_thumb"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>原图样式:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="原图样式名"id="bucket_style_original"name="bucket_style_original"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>原图水印样式:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="原图加水印样式名"id="bucket_style_water"name="bucket_style_water"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>适应宽度样式:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="适应宽度样式名"id="bucket_style_fixwidth"name="bucket_style_fixwidth"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>适应高度样式:</label><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="适应高度样式名"id="bucket_style_fixheight"name="bucket_style_fixheight"rangelength="[3,100]"required></div></div><divclass="form-group"><divclass="col-sm-9 col-sm-offset-3"><buttontype="submit"class="btn btn-success disabled"disabled="true">提交数据</button></div></div>
</form>

对表单进行前端验证时不要忘了引入jquery-validation插件。

$(function() {//初始化checkbox的icheck样式$('input[type="checkbox"],input[type="radio"]').iCheck({checkboxClass:'icheckbox_minimal-blue',radioClass   :'iradio_minimal-blue'})//只有当表单中有数据变化时,提交按钮才可用$("form").children().bind('input propertychange ifChecked ifUnchecked',function() {$(":submit").removeClass('disabled').removeAttr('disabled');});$("form").validate({rules: {bucket_domain: {url:true}},submitHandler:function(form) {//当验证通过时执行ajax提交ajax_post("{:url('index/picture/do_bucket_add')}",$("form").serialize());}});});

(3) bucket_edit.html

<formmethod="post"class="form-horizontal"><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>空间名称(只读):</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_name}"type="text"class="form-control"id="bucket_name"name="bucket_name"readonly="true"></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>空间域名:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_domain}"type="text"class="form-control"placeholder="空间域名,http://.../形式,以/结尾"id="bucket_domain"name="bucket_domain"rangelength="[4,100]"required></div></div>        <divclass="form-group"><labelclass="control-label col-sm-3">描述:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_description}"type="text"class="form-control"placeholder="文字描述"id="bucket_description"name="bucket_description"maxlength="100"></div></div><divclass="form-group"><labelclass="control-label col-sm-3">默认空间:</label><divclass="col-sm-9"><input{eq name="bucket.bucket_default"value="1"} checked="true"{/eq} type="checkbox"id="bucket_default"name="bucket_default" />勾选为默认,只可以有1个默认空间</div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>缩略图样式:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_style_thumb}"type="text"class="form-control"placeholder="缩略图样式名"id="bucket_style_thumb"name="bucket_style_thumb"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>原图样式:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_style_original}"type="text"class="form-control"placeholder="原图样式名"id="bucket_style_original"name="bucket_style_original"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>原图水印样式:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_style_water}"type="text"class="form-control"placeholder="原图加水印样式名"id="bucket_style_water"name="bucket_style_water"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>适应宽度样式:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_style_fixwidth}"type="text"class="form-control"placeholder="适应宽度样式名"id="bucket_style_fixwidth"name="bucket_style_fixwidth"rangelength="[3,100]"required></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>适应高度样式:</label><divclass="col-sm-9"><inputvalue="{$bucket.bucket_style_fixheight}"type="text"class="form-control"placeholder="适应高度样式名"id="bucket_style_fixheight"name="bucket_style_fixheight"rangelength="[3,100]"required></div></div><divclass="form-group"><divclass="col-sm-9 col-sm-offset-3"><buttontype="submit"class="btn btn-success disabled"disabled="true">提交数据</button></div></div>
</form>

$(function() {//初始化checkbox的icheck样式$('input[type="checkbox"],input[type="radio"]').iCheck({checkboxClass:'icheckbox_minimal-blue',radioClass   :'iradio_minimal-blue'})//只有当表单中有数据变化时,提交按钮才可用$("form").children().bind('input propertychange ifChecked ifUnchecked',function() {$(":submit").removeClass('disabled').removeAttr('disabled');});$("form").validate({rules: {bucket_domain: {url:true}},submitHandler:function(form) {//当验证通过时执行ajax提交ajax_post("{:url('index/picture/do_bucket_edit')}",$("form").serialize());}});});

(4) picture.html

<divclass="nav-tabs-custom"><ulid="main-nav"class="nav nav-tabs"><liclass="header">空间 <iclass="fa fa-arrow-right"></i> </li>{volist name="bucketlist" id="vo"}<liclass="{if condition='$vo.bucket_default eq 1'}active{/if}"><ahref="#{$vo.bucket_name}"data-toggle="tab">{$vo.bucket_name}</a></li>{/volist}</ul><divid="main-nav-tabs"class="tab-content">{volist name="bucketlist" id="vo"}<divclass="tab-pane {eq name='vo.bucket_default' value='1'}active{/eq}"id="{$vo.bucket_name}"><divclass="row"><divclass="col-xs-3"><ahref="javascript:;"onclick="modal_show_iframe('上传图片','{:url("index/picture/add",["bucket"=>$vo.bucket_name])}','')" class="btn btn-primary"><iclass="fa fa-fw fa-plus-square"></i> 上传图片</a> </div></div><divclass="row mt-3">{volist name="vo.child" id="vo_c" mod="6" empty="没有图片"}<divclass="col-xs-6 col-md-4 col-lg-2"><divclass="panel {eq name='vo_c.picture_protected' value='1'}panel-danger{else/}panel-info{/eq}"><divclass="panel-heading ellipsis"><spantitle="{$vo_c.picture_name}">{$vo_c.picture_name}</span></div><divclass="panel-body"><ahref="{$vo.bucket_domain}{$vo_c.picture_key}-{$vo.bucket_style_water}"data-lightbox="qiniu-image"><imgclass="lazy img-responsive"src="__STATIC__/img/loading-0.gif"data-src="{$vo.bucket_domain}{$vo_c.picture_key}-{$vo.bucket_style_thumb}"data-original="{$vo.bucket_domain}{$vo_c.picture_key}-{$vo.bucket_style_thumb}"alt=""></a></div><divclass="panel-footer ellipsis"><spantitle="{$vo_c.picture_description}">{$vo_c.picture_description}</span><br/><spantitle="{$vo_c.create_time}">{$vo_c.create_time}</span><br/><spantitle="{$vo_c.update_time}">{$vo_c.update_time}</span><br/><spanclass="pull-right"><ahref="javascript:;"onclick="modal_show_iframe('编辑图片','{:url("index/picture/edit",["id"=>$vo_c.picture_id])}','')" title="编辑"><iclass="fa fa-edit fa-fw"></i></a><ahref="javascript:;"onclick="ajax_post_confirm('{:url("index/picture/do_picture_delete")}',{id:'{$vo_c.picture_id}'},'{$vo_c.picture_name}','删除');" title="删除"><iclass="fa fa-trash fa-fw"></i></a></span></div></div></div>{eq name="mod" value="5"}</div><divclass="row">{/eq}{/volist}</div></div>{/volist}<!--/.tab-pane--></div><!--/.tab-content-->
</div>

$(function() {//图片lazyload懒加载$("img.lazy").lazyload();//如果没有默认空间,则默认激活第1个空间if(!$("#main-nav-tabs .tab-pane.active")==false) {$("#main-nav a:first").tab("show");}
});

引入lazyload组件以实现图片的懒加载,详细信息详见网址:

https://appelsiini.net/projects/lazyload

引入lightbox2组件以实现图片预览,具体信息详见网址:

https://lokeshdhakar.com/projects/lightbox2/

(5) picture_add.html

<formmethod="post"enctype="multipart/form-data"class="form-horizontal"><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>选择空间</label><divclass="col-sm-9"><selectname="bucket_name"class="form-control">{volist name="bucketlist" id="vo"}<optionvalue="{$vo.bucket_name}"{empty name="bucket"}{eq name="vo.bucket_default"value="1"} selected="true"{/eq}{else/}{eq name="vo.bucket_name"value="$bucket"} selected="true"{/eq}{/empty}>{$vo.bucket_name}</option>{/volist}</select></div></div><divclass="form-group"><labelclass="control-label col-sm-3"><spanclass="text-red">*</span>图片文件</label><divclass="col-sm-9"><inputtype="file"class="form-control"placeholder="请选择图片文件"id="picture_file"name="picture_file"accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"required/></div></div><divclass="form-group"><lableclass="control-label col-sm-3">图片标题</lable><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="给图片设定一个标题,空白默认文件名"id="picture_name"name="picture_name" /></div></div><divclass="form-group"><lableclass="control-label col-sm-3">图片描述</lable><divclass="col-sm-9"><inputtype="text"class="form-control"placeholder="给图片编辑一段描述"id="picture_description"name="picture_description" /></div></div><divclass="form-group"><lableclass="control-label col-sm-3">权限保护</lable><divclass="col-sm-9"><label><inputtype="checkbox"id="picture_protected"name="picture_protected" />勾选表示设置权限保护(轻易不要勾选)</label>            </div></div><divclass="form-group"><divclass="col-sm-9 col-sm-offset-3"><buttontype="submit"class="btn btn-success disabled"disabled="true">提交数据</button></div></div>
</form>

$(function() {//初始化checkbox的icheck样式$('input[type="checkbox"]').iCheck({checkboxClass:'icheckbox_minimal-blue',radioClass   :'iradio_minimal-blue'})//只有当表单中有数据变化时,提交按钮才可用$("form").children().bind('input propertychange ifChecked ifUnchecked',function() {$(":submit").removeClass('disabled').removeAttr('disabled');});$("form").validate({rules: {},submitHandler:function(form) {//当验证通过时执行ajax提交
upload();}});});functionupload() {var formData = newFormData();var file = $("[name='picture_file']")[0].files[0];formData.append("picture_file", file);formData.append("bucket_name", $("[name='bucket_name']").val());formData.append("picture_name", $("[name='picture_name']").val());formData.append("picture_description", $("[name='picture_description']").val());formData.append("picture_protected", $("[name='picture_protected']").is(':checked') ? 1 : 0);$.ajax({url:"{:url('index/picture/do_picture_add')}",type:'POST',data: formData,//告诉jQuery不要去处理发送的数据processData: false,//告诉jQuery不要去设置Content-Type请求头contentType: false,beforeSend:function() {var loading = layer.load(1, {shade: [0.1,'#fff'] //0.1透明度的白色背景
});},success:function(data) {console.log(data);layer.closeAll();//当ajax请求执行成功时执行if (data.status == true) {//返回result对象中的status元素值为1表示数据插入成功layer.msg(data.message, {icon: 6, time: 2000});    //使用H-ui的浮动提示框,2秒后自动消失setTimeout(function() {parent.location.reload();},2000);    //2秒后对父页面执行刷新(相当于关闭了弹层同时更新了数据)} else{//返回result对象的status值不为1,表示数据插入失败layer.alert(data.message+"<p>请自行刷新页面</p>", {icon: 5});//页面停留在这里,不再执行任何动作
}},error:function(data) {console.log(data);}});}

(6) picture_edit.html

<formmethod="post"enctype="multipart/form-data"class="form-horizontal"><divclass="form-group hide"><lableclass="control-label col-sm-3">图片ID<spanclass="text-red">*</span></lable><divclass="col-sm-9"><inputvalue="{$picture.picture_id}"type="text"class="form-control"id="picture_id"name="picture_id"readonly="true"required/></div></div><divclass="form-group"><lableclass="control-label col-sm-3">图片标题<spanclass="text-red">*</span></lable><divclass="col-sm-9"><inputvalue="{$picture.picture_name}"type="text"class="form-control"placeholder="给图片设定一个标题"id="picture_name"name="picture_name"required/></div></div><divclass="form-group"><lableclass="control-label col-sm-3">图片描述</lable><divclass="col-sm-9"><inputvalue="{$picture.picture_description}"type="text"class="form-control"placeholder="给图片编辑一段描述"id="picture_description"name="picture_description" /></div></div><divclass="form-group"><lableclass="control-label col-sm-3">权限保护</lable><divclass="col-sm-9"><label><input{eq name="picture.picture_protected"value="1"} checked="true"{/eq} type="checkbox"id="picture_protected"name="picture_protected" />勾选表示设置权限保护(轻易不要勾选)</label>            </div></div><divclass="form-group"><divclass="col-sm-9 col-sm-offset-3"><buttontype="submit"class="btn btn-success disabled"disabled="true">提交数据</button></div></div>
</form>

js部分省略,详见github。

(7) index/controller/Index.php

无逻辑处理,省略,详见github。

(8) index/controller/Picture.php

class Picture extendsBase
{public functionindex(){$this->view->assign('pagetitle', '图片管理');//加载bucket列表$bucketlist = BucketModel::all(function($query) {$query->order(['bucket_default'=>'desc', 'bucket_name'=>'asc']);});//加载bucket里的图片$picturelist;//遍历bucketforeach($bucketlist as $n=>$bucket) {$picture = newPictureModel;$picturelist = $picture->where(['bucket_name'=>$bucket->bucket_name])->order(['create_time'=>'desc'])->select();$bucketlist[$n]['child'] = $picturelist;}$this->view->assign('bucketlist', $bucketlist);return $this->view->fetch('picture/picture');}/*** 加载添加图片页面*/public functionadd(){$this->view->assign('pagetitle', '上传图片');$bucket = input('?bucket') ? input('bucket') : '';$this->view->assign('bucket', $bucket);$bucketlist = BucketModel::all(function($query) {$query->order(['bucket_default'=>'desc', 'bucket_name'=>'asc']);});$this->view->assign('bucketlist', $bucketlist);return $this->view->fetch('picture/picture_add');}/*** 加载编辑图片页面* @return [type] [description]*/public functionedit(){$this->view->assign('pagetitle', '编辑图片信息');if(!input('?id')) {$this->error('参数错误');return;}$id = input('id');$picture = PictureModel::get($id);if(!$picture) {$this->error('参数错误');return;}$this->view->assign('picture', $picture);return $this->view->fetch('picture/picture_edit');}/*** 执行编辑图片操作* @return [type] [description]*/public functiondo_picture_edit(){$res = newRes;$res->data =input();$res->data['picture_protected'] = input('?picture_protected') ? 1 : 0;try{$picture = newPictureModel;$res->data_row_count = $picture->isUpdate(true)->allowField(true)->save(['picture_name'=>$res->data['picture_name'],'picture_description'=>$res->data['picture_description'],'picture_protected'=>$res->data['picture_protected']],['picture_id'=>$res->data['picture_id']]);if($res->data_row_count) {$res->success();}}catch (\Exception $e) {$res->faild($e->getMessage());}return $res;}/*** 执行添加图片操作* @return [type] [description]*/public functiondo_picture_add(){$res = newRes;$picture_file = request()->file('picture_file');$picture = newPictureModel;$picture->bucket_name = input('bucket_name');$picture->picture_name = input('picture_name')?:$picture_file->getInfo('name');$picture->picture_description = input('picture_description')?:$picture->picture_name;$picture->picture_protected = input('picture_protected');//由于demo中没做登录部分,所以这里获取不到值// $picture->admin_id = Session::has('admin_infor')?Session::get('admin_infor')->admin_id:'';if($picture_file) {//创建PictureService对象实例$pservice = new\app\common\controller\PictureService;try{//调用up_file方法向指定空间上传图片$res = $pservice->up_picture($picture_file, $picture);}catch(\Exception $e) {$res->failed($e->getMessage());}}return $res;}/*** 执行删除图片的操作* @return [type] [description]*/public functiondo_picture_delete(){$res = newRes;if(!input('?id')) {//未取到id参数$res->failed('参数错误');return $res;}$id = input('id');try{$res->data = PictureModel::get($id);if(!$res->data) {//取到的id参数没有对应的记录$res->failed('参错错误');return $res;}if($res->data['picture_protected']) {$res->failed('不能删除受保护的图片');return $res;}//创建QiniuService对象实例$qservice = new\app\common\controller\QiniuService;//调用delete_file方法删除指定bucket和指定key的文件$res = $qservice->delete_file($res->data['bucket_name'], $res->data['picture_key']);if($res->status) {//文件删除成功,开始删除数据PictureModel::where(['picture_id'=>$id])->delete();$res->append_message('<li>数据库记录删除成功</li>');}}catch(\Exception $e) {$res->failed($e->getMessage());}return $res;}/*** 加载空间管理页面* @return [type] [description]*/public functionbucket(){$this->view->assign('pagetitle','存储空间');$bucketlist = BucketModel::all(function($query) {$query->order(['bucket_default'=>'desc', 'bucket_name'=>'asc']);});$this->view->assign('list', $bucketlist);$this->view->assign('count', count($bucketlist));return $this->view->fetch('picture/bucket');}/*** 加载添加空间页面* @return [type] [description]*/public functionbucket_add(){$this->view->assign('pagetitle', '添加存储空间');return $this->view->fetch('picture/bucket_add');}/*** 执行添加空间操作* @return [type] [description]*/public functiondo_bucket_add(){$res = newRes;$res->data =input();$res->data['bucket_default'] = input('?bucket_default') ? 1 : 0;$bucket = newBucketModel;$validate = Loader::validate('Bucket');if(!$validate->check($res->data)) {$res->failed($validate->getError());return $res;}if($res->data['bucket_default']) {$default = BucketModel::get(['bucket_default'=>1]);//单独验证只可以有一条默认空间if($default) {$res->failed('只能有1个默认空间:已经存在默认空间'.$default->bucket_name);return $res;}}try{$res->data_row_count = $bucket->isUpdate(false)->allowField(true)->save(['bucket_name'        =>    $res->data['bucket_name'],'bucket_domain'        =>    $res->data['bucket_domain'],'bucket_description'=>    $res->data['bucket_description'],'bucket_default'=>    $res->data['bucket_default'],'bucket_style_thumb'=>    $res->data['bucket_style_thumb'],'bucket_style_original'=>    $res->data['bucket_style_original'],'bucket_style_water'=>    $res->data['bucket_style_water'],'bucket_style_fixwidth'=>    $res->data['bucket_style_fixwidth'],'bucket_style_fixheight'=>    $res->data['bucket_style_fixheight'],]);if($res->data_row_count) {$res->success();}}catch(\Exception $e) {$res->failed($e->getMessage());}return $res;}/*** 加载编辑空间页面* @return [type] [description]*/public functionbucket_edit(){$this->view->assign('pagetitle', '编辑存储空间');if(!input('?name')) {$this->error('参数错误');return;}$name = input('name');$bucket = BucketModel::get(['bucket_name'=>$name]);if(!$bucket) {$this->error('参数错误');return;}$this->view->assign('bucket', $bucket);return $this->view->fetch('picture/bucket_edit');}/*** 执行修改空间(描述)操作* @return [type] [description]*/public functiondo_bucket_edit(){$res = newRes;$res->data =input();$res->data['bucket_default'] = input('?bucket_default') ? 1 : 0;$validate = Loader::validate('Bucket');if(!$validate->scene('edit')->check($res->data)) {$res->failed($validate->getError());return $res;}$bucket = newBucketModel;if($res->data['bucket_default']) {$default = $bucket->where('bucket_default', 'eq', 1)->where('bucket_name','neq',$res->data['bucket_name'])->find();if($default) {$res->failed('只能有1个默认空间:已经存在默认空间'.$default->bucket_name);return $res;}}try{$res->data_row_count = $bucket->isUpdate(true)->allowField(true)->save(['bucket_domain'=>$res->data['bucket_domain'],'bucket_description'=>$res->data['bucket_description'],'bucket_default'=>$res->data['bucket_default'],'bucket_style_thumb'=>$res->data['bucket_style_thumb'],'bucket_style_original'=>$res->data['bucket_style_original'],'bucket_style_water'=>$res->data['bucket_style_water'],'bucket_style_fixwidth'=>$res->data['bucket_style_fixwidth'],'bucket_style_fixheight'=>$res->data['bucket_style_fixheight'],], ['bucket_name'=>$res->data['bucket_name']]);if($res->data_row_count) {$res->success();}else{$res->failed('未更改任何数据');}}catch(\Exception $e) {$res->failed($e->getMessage());}return $res;}/*** 执行删除空间(非默认)操作* @return [type] [description]*/public functiondo_bucket_delete(){$res = newRes;$name = input('?name') ? input('name') : '';$bucket = BucketModel::get(['bucket_name'=>$name]);$res->data = $bucket;if(empty($bucket)) {$res->failed("参数错误");return $res;}if($bucket->bucket_default==1) {$res->failed("默认空间不允许删除");return $res;}try{$res->data_row_count = BucketModel::where(['bucket_name'=>$name])->delete();    //执行真删除$res->success();}catch(\Exception $e) {$res->failed($e->getMessage());}return $res;}
}

(9) common/controller/QiniuService.php

QiniuService并没有继承common/controller/base,因为它不需要使用thinkphp的controller特性。

classQiniuService
{/*** 向七牛云存储获取指定bucket的token* @param  string $bucket [指定bucket名称]* @return [type]         [description]*/private function get_token($bucket){$access_key = Env::get('qiniu.access_key');$secret_key = Env::get('qiniu.secret_key');$auth = new \Qiniu\Auth($access_key, $secret_key);$upload_token = $auth->uploadToken($bucket);return $upload_token;}private functiongenerate_auth(){$access_key = Env::get('qiniu.access_key');$secret_key = Env::get('qiniu.secret_key');$auth = new \Qiniu\Auth($access_key, $secret_key);return $auth;}public function delete_file($bucket, $key){$res = newRes;try{$auth = $this->generate_auth();$bucketManager = new \Qiniu\Storage\BucketManager($auth);$config = new\Qiniu\Config();$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);$err = $bucketManager->delete($bucket, $key);//dump($err->getResponse('statusCode')->statusCode);/*HTTP状态码    说明298    部分操作执行成功400    请求报文格式错误包括上传时,上传表单格式错误。例如incorrect region表示上传域名与上传空间的区域不符,此时需要升级 SDK 版本。401    认证授权失败错误信息包括密钥信息不正确;数字签名错误;授权已超时,例如token not specified表示上传请求中没有带 token ,可以抓包验证后排查代码逻辑; token out of date表示 token 过期,推荐 token 过期时间设置为 3600 秒(1 小时),如果是客户端上传,建议每次上传从服务端获取新的 token;bad token表示 token 错误,说明生成 token 的算法有问题,建议直接使用七牛服务端 SDK 生成 token。403    权限不足,拒绝访问。例如key doesn't match scope表示上传文件指定的 key 和上传 token 中,putPolicy 的 scope 字段不符。上传指定的 key 必须跟 scope 里的 key 完全匹配或者前缀匹配;ExpUser can only upload image/audio/video/plaintext表示账号是体验用户,体验用户只能上传文本、图片、音频、视频类型的文件,完成实名认证即可解决;not allowed表示您是体验用户,若想继续操作,请先前往实名认证。404    资源不存在包括空间资源不存在;镜像源资源不存在。405    请求方式错误主要指非预期的请求方式。406    上传的数据 CRC32 校验错误413    请求资源大小大于指定的最大值419    用户账号被冻结478    镜像回源失败主要指镜像源服务器出现异常。502    错误网关503    服务端不可用504    服务端操作超时573    单个资源访问频率过高579    上传成功但是回调失败包括业务服务器异常;七牛服务器异常;服务器间网络异常。需要确认回调服务器接受 POST 请求,并可以给出 200 的响应。599    服务端操作失败608    资源内容被修改612    指定资源不存在或已被删除614    目标资源已存在630    已创建的空间数量达到上限,无法创建新空间。631    指定空间不存在640    调用列举资源(list)接口时,指定非法的marker参数。701    在断点续上传过程中,后续上传接收地址不正确或ctx信息已过期。*/if($err) {if($err->getResponse('statusCode')->statusCode==612) {//指定资源不存在或已被删除$res->success('目标文件已不存在');}else{$res->failed($err->message());}}else{$res->success();}}catch (\Exception $e) {$res->failed($e->getMessage());}return $res;}/*** 向指定七牛云存储空间上传文件* @param  [type] $bucket [指定存储空间bucket名称]* @param  [type] $file   [需上传的文件]* @return [type]         [Res对象实例]*/public function up_file($bucket, $file = null){$token = $this->get_token($bucket);$res = newRes;$res->data = '';$res->result = ['token'=>$token];if($file) {//要上传图片的本地路径$file_path = $file->getRealPath();//文件名后缀$ext = pathinfo($file->getInfo('name'),PATHINFO_EXTENSION);//文件前缀(类似文件夹)$prefix = str_replace("-","",date('Y-m-d/'));//上传后保存的文件名(无后缀)$file_name = uniqid();//上传后的完整文件名(含前缀后缀)$key = $prefix.$file_name.'.'.$ext;//域名$domain = Bucket::get(['bucket_name'=>$bucket])->bucket_domain;//初始化UploadManager对象并进行文件上传$upload_manager = new\Qiniu\Storage\UploadManager();//调用UploadManager的putFile方法进行文件上传list($ret, $err) = $upload_manager->putFile($token, $key, $file_path);if($err!==null) {$res->failed($err);}else{$res->success();                $res->result['domain'] = $domain;$res->result['key'] = $ret['key'];$res->result['hash'] = $ret['hash'];$res->result['bucket'] = $bucket;}}else{$res->failed('未接收到文件');}return $res;}/*** 从服务器传输文件到七牛云* @param  [type] $bucket    目标bucket* @param  [type] $file_path 要传输文件的服务器路径* @return [type]            res*/public function transfer_file($bucket, $file_path){//构建鉴权对象$auth = $this->generate_auth();//生成上传 Token$token = $auth->uploadToken($bucket);//文件后缀$ext = pathinfo($file_path,PATHINFO_EXTENSION);//文件前缀(类似文件夹)$prefix = str_replace("-","",date('Y-m-d/'));//上传到七牛后保存的文件名(不带后缀)$file_name = uniqid();//上传后的完整文件名(含前缀后缀)$key = $prefix.$file_name.'.'.$ext;//域名$domain = Bucket::get(['bucket_name'=>$bucket])->bucket_domain;        $res = newRes;try{//初始化 UploadManager 对象并进行文件的上传。$uploadMgr = new\Qiniu\Storage\UploadManager();//调用 UploadManager 的 putFile 方法进行文件的上传。list($ret, $err) = $uploadMgr->putFile($token, $key, '.'.$file_path);if ($err !== null) {$res->failed();$res->result['obj'] = $err;}else{$res->success();$res->result['obj'] = $ret;$res->result['domain'] = $domain;$res->result['key'] = $ret['key'];$res->result['hash'] = $ret['hash'];$res->result['bucket'] = $bucket;}}catch (\Exception $e) {$res->failed($e->getMessage());}return $res;        }/*** 获取七牛云指定bucket存储空间的文件列表* @param  [type]  $bucket [指定存储空间名称]* @param  string  $marker [上次列举返回的位置标记,作为本次列举的起点信息]* @param  string  $prefix [要列取文件的公共前缀]* @param  integer $limit  [本次列举的条目数]* @return [type]          [description]*/public function list_file($bucket, $marker='', $prefix='', $limit=100){$auth = $this->generate_auth();$bucketManager = new \Qiniu\Storage\BucketManager($auth);        $delimiter = '';//列举文件list($ret, $err) = $bucketManager->listFiles($bucket, $prefix, $marker, $limit, $delimiter);if ($err !== null) {$result = $err;}else{if (array_key_exists('marker', $ret)) {echo "Marker:" . $ret["marker"] . "\n";}$result = $ret;}return $result;}
}

(10) common/controller/PictureService.php

class PictureService extendsCommonBase
{/*** 从数据库中找到第1个默认bucket* @return [type] [description]*/private functiondefault_bucket(){$bucket = newBucket;//向数据库查询bucket_default为1的记录$default_bucket = $bucket->where(['bucket_default'=>1])->find();//如果没有bucket_default为1的记录,再尝试取第1条bucket记录if(!$default_bucket) {$default_bucket = $bucket->where('1=1')->find();}//如果实在取不到,这里就算了,返回吧return $default_bucket;}public function up_picture($file, $picture){$res = newRes;if(empty($picture->toArray()['bucket_name'])) {$bucket = $this->default_bucket();if($bucket) {$picture->bucket_name = $this->default_bucket()->bucket_name;}else{$res->failed('无法获取bucket信息');return $res;}}if(empty($picture->toArray()['picture_name'])) {$picture->picture_name = $file->getInfo('name');}if(empty($picture->toArray()['picture_description'])) {$picture->picture_description = $picture->picture_name;}if($file) {//创建QiniuService对象实例$qservice = newQiniuService;try{//调用up_file方法向指定空间上传图片$res = $qservice->up_file($picture->bucket_name, $file);if($res->status) {//上传成功,写入数据库$picture->picture_key = $res->result['key'];//在我的项目中有一个自动生成全局唯一且递增ID的方法,但是demo中没做相关配置部分//demo中将picture_id直接设置成自增ID了//$picture->picture_id = $this->apply_full_global_id_str();$res_db = newRes;$res_db->data_row_count = $picture->isUpdate(false)->allowField(true)->save();if($res_db->data_row_count) {//写入数据库成功$res_db->success();$res_db->data = $picture;}//将写入数据库的结果作为返回结果的一个属性$res->result["db"] = $res_db;}}catch(\Exception $e) {$res->failed($e->getMessage());}}return $res;}public function up_scrawl($ext = null, $content = null, $path = null){//保存图片到服务器,取得服务器路径$file_path = $this->save_picture($ext, $content, $path);//传输服务器图片到七牛云,取得返回的url$url = $file_path;$res = newRes;$picture = newPicture;$picture->bucket_name = $this->default_bucket()->bucket_name;$picture->picture_name = pathinfo($file_path,PATHINFO_BASENAME);$picture->picture_description = $picture->picture_name;try{$qservice = newQiniuService;            $res = $qservice->transfer_file($picture->bucket_name, $file_path);if($res->status) {//保存数据库信息$picture->picture_key = $res->result['key'];//在我的项目中有一个自动生成全局唯一且递增ID的方法,但是demo中没做相关配置部分//demo中将picture_id直接设置成自增ID了// $picture->picture_id = $this->apply_full_global_id_str();$res_db = newRes;$res_db->data_row_count = $picture->isUpdate(false)->allowField(true)->save();if($res_db->data_row_count) {//写入数据库成功$res_db->success();$res_db->data = $picture;}//将写入数据库的结果作为返回结果的一个属性$res->result["db"] = $res_db;//准备url// bucket对应的域名$url = $res->result['domain'];//图片在bucket中的key$url .= $res->result['key'];//默认插入水印板式$url .= '-'.Bucket::get(['bucket_name'=>$res->result['bucket']])->bucket_style_water;}}catch(\Exception $e) {$res->failed($e->getMessage());$url = '';}//删除服务器图片unlink('.'.$file_path);//返回的是七牛云上的urlreturn $url;}/*** 在服务器保存图片文件* @param  [type] $ext     [description]* @param  [type] $content [description]* @param  [type] $path    [description]* @return [type]          [description]*/private function save_picture($ext = null, $content = null, $path = null){$full_path = '';if ($ext && $content) {do{$full_path = $path . uniqid() . '.' . $ext;}while (file_exists($full_path));$dir = dirname($full_path);if (!is_dir($_SERVER['DOCUMENT_ROOT'].$dir)) {mkdir($_SERVER['DOCUMENT_ROOT'].$dir, 0777, true);}file_put_contents($_SERVER['DOCUMENT_ROOT'].$full_path, $content);}return $full_path;}
}

(11) api/controller/Ueditor.php

class Ueditor extendsApiBase
{private $uploadfolder='/upload/';   //上传地址private $scrawlfolder='/upload/_scrawl/';   //涂鸦保存地址private $catchfolder='/upload/_catch/';   //远程抓取地址private $configpath='/static/lib/ueditor/utf8-php/php/config.json';    //前后端通信相关的配置private $config;public functionindex(){$this->type=input('edit_type','');date_default_timezone_set("Asia/chongqing");error_reporting(E_ERROR);header("Content-Type: text/html; charset=utf-8");$CONFIG = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents($_SERVER['DOCUMENT_ROOT'].$this->configpath)), true);$this->config=$CONFIG;$action = input('action');switch ($action) {case 'config':$result =  json_encode($CONFIG);break;/*上传图片*/case 'uploadimage':$result = $this->_qiniu_upload();break;/*上传涂鸦*/case 'uploadscrawl':$result = $this->_upload_scrawl();break;/*上传视频,demo暂时没有实现,可以查看其他文章*/case 'uploadvideo':$result = $this->_upload(array('maxSize' => 1073741824,/*1G*/'exts'=>array('mp4', 'avi', 'wmv','rm','rmvb','mkv')));break;/*上传文件,demo暂时没有实现,可以查看其他文章*/case 'uploadfile':$result = $this->_upload(array('exts'=>array('jpg', 'gif', 'png', 'jpeg','txt','pdf','doc','docx','xls','xlsx','zip','rar','ppt','pptx',)));break;/*列出图片*/case 'listimage':$result = $this->_qiniu_list($action);break;/*列出文件,demo暂时没有实现,可以查看其他文章*/case 'listfile':$result = $this->_list($action);break;/*抓取远程文件,demo暂时没有实现,可以查看其他文章*/case 'catchimage':$result = $this->_upload_catch();break;default:$result = json_encode(array('state'=> '请求地址出错'));break;}/*输出结果*/if (isset($_GET["callback"]) && false) {if (preg_match("/^[\w_]+$/", $_GET["callback"])) {echo htmlspecialchars($_GET["callback"]) . '(' . $result . ')';}else{echo json_encode(array('state'=> 'callback参数不合法'));}}else{exit($result) ;}}private function _qiniu_upload($config=array()){$title = '';$url='';if(!empty($config)){$this->config=array_merge($this->config,$config);;}$file = request()->file('upfile');if($file){$picture = newPicture;//demo中暂时关闭关于admin的处理// $picture->admin_id = Session::has('admin_infor')?Session::get('admin_infor')->admin_id:'';$pservice = newPictureService;$res = $pservice->up_picture($file, $picture);if($res->status) {//bucket对应的域名$url = $res->result['domain'];//图片在bucket中的key$url .= $res->result['key'];//默认插入水印板式$url .= '-'.Bucket::get(['bucket_name'=>$res->result['bucket']])->bucket_style_water;$title = $res->result['key'];$state = 'SUCCESS';}else{$state = $res->message();}}else{$state = '未接收到文件';}$response=array("state" => $state,"url" => $url,"title" => $title,"original" =>$title,);return json_encode($response);}private function_upload_scrawl(){$data = input('post.' . $this->config ['scrawlFieldName']);$url='';$title = '';$oriName = '';if (empty ($data)) {$state= 'Scrawl Data Empty!';}else{$pservice = newPictureService;//在服务器保存图片文件$url = $pservice->up_scrawl('png', base64_decode($data), $this->scrawlfolder);if ($url) {$state = 'SUCCESS';}else{$state = 'Save scrawl file error!';}}$response=array("state" => $state,"url" => $url,"title" => $title,"original" =>$oriName ,);return json_encode($response);}private function _qiniu_list($action){/*判断类型*/switch ($action) {/*列出文件*/case 'listfile':$allowFiles = $this->config['fileManagerAllowFiles'];$listSize = $this->config['fileManagerListSize'];$prefix='/';break;/*列出图片*/case 'listimage':default:$allowFiles = $this->config['imageManagerAllowFiles'];$listSize = $this->config['imageManagerListSize'];$prefix='/';}//这里暂时没有用20190606$start = 0;//准备文件列表$list =[];$picture = Picture::all();foreach($picture as $n=>$p) {$list[] = array('url'=>$p->bucket->bucket_domain.$p->picture_key.'-'.$p->bucket->bucket_style_thumb,'title'=>$p->picture_name,'url_original'=>$p->bucket->bucket_domain.$p->picture_key.'-'.$p->bucket->bucket_style_water,);}/*返回数据*/$result = json_encode(array("state" => "SUCCESS","list" => $list,"start" => $start,"total" => count($list)));return $result;}/*** 遍历获取目录下的指定类型的文件* @param string $path* @param string $allowFiles* @param array $files* @return array*/function getfiles($path, $allowFiles, &$files = array()){if (!is_dir($path)) return null;if(substr($path, strlen($path) - 1) != '/') $path .= '/';$handle = opendir($path);while (false !== ($file = readdir($handle))) {if ($file != '.' && $file != '..') {$path2 = $path . $file;if (is_dir($path2)) {$this->getfiles($path2, $allowFiles, $files);}else{if (preg_match("/\.(".$allowFiles.")$/i", $file)) {$files[] = array('url'=> substr($path2, strlen($_SERVER['DOCUMENT_ROOT'])),//'document_root'=> $_SERVER['DOCUMENT_ROOT'],// 'root_path'=> ROOT_PATH,// 'path2'=> $path2,// 'path'=> $path,// 'mtime'=> filemtime($path2)
);}}}}return $files;}
}

(12) 修改ueditor中的代码:

path-to-ueditor/ueditor.config.js

window.UEDITOR_CONFIG ={//为编辑器实例添加一个路径,这个不能被注释
UEDITOR_HOME_URL: URL//服务器统一请求接口路径

// 修改为自定义的serverUrl,demo中就是/api/ueditor/index

        , serverUrl: 

"/api/ueditor/index"

        //工具栏上的所有的功能按钮和下拉框,可以在new编辑器的实例时选择自己需要的重新定义//, toolbars: [[//'fullscreen', 'source', '|',//'undo', 'redo', '|',//'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|',//'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',//'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',//'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',//'directionalityltr', 'directionalityrtl', 'indent', '|',//'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|',//'touppercase', 'tolowercase', '|',//'link', 'unlink', 'anchor', '|',//'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',//'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', 'music', 'attachment', 'map', 'gmap', 'insertframe', 'insertcode', 'webapp', 'pagebreak', 'template', 'background', '|',//'horizontal', 'date', 'time', 'spechars', 'snapscreen', 'wordimage', '|',//'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', 'charts', '|',//'print', 'preview', 'searchreplace', 'drafts', 'help'//]]

// 修改:关闭不需要的按钮

, toolbars: [['fullscreen', 'source', '|','undo', 'redo', '|','bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|','forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|','rowspacingtop', 'rowspacingbottom', 'lineheight', '|','customstyle', 'paragraph', 'fontfamily', 'fontsize', '|','directionalityltr', 'directionalityrtl', 'indent', '|','justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|','touppercase', 'tolowercase', '|','link', 'unlink', 'anchor', '|','imagenone', 'imageleft', 'imageright', 'imagecenter', '|','simpleupload', 'insertimage', 'emotion', 'scrawl', 'map', 'insertframe', 'insertcode', 'pagebreak', 'template', '|','horizontal', 'date', 'time', 'spechars', '|','inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', 'charts', '|','print', 'preview', 'searchreplace', 'drafts', 'help']]

path-to-ueditor/ueditor.all.js

UE.commands['insertimage'] ={execCommand:function(cmd, opt) {……if (img && /img/i.test(img.tagName) && (img.className != "edui-faked-video" || img.className.indexOf("edui-upload-video")!=-1) && !img.getAttribute("word_img")) {……var floatStyle = first['floatStyle'];}else{var html = [], str = '', ci;ci= opt[0];if (opt.length == 1) {unhtmlData(ci);

// 修改:添加bootstrap的img-responsive样式以支持响应式图片

                str = '<img

class="img-responsive"

 src="' + ci.src + '" ' + (ci._src ? ' _src="' + ci._src + '" ' : '') +(ci.width? 'width="' + ci.width + '" ' : '') +(ci.height? ' height="' + ci.height + '" ' : '') +(ci['floatStyle'] == 'left' || ci['floatStyle'] == 'right' ? ' style="float:' + ci['floatStyle'] + ';"' : '') +(ci.title&& ci.title != "" ? ' title="' + ci.title + '"' : '') +(ci.border&& ci.border != "0" ? ' border="' + ci.border + '"' : '') +(ci.alt&& ci.alt != "" ? ' alt="' + ci.alt + '"' : '') +(ci.hspace&& ci.hspace != "0" ? ' hspace = "' + ci.hspace + '"' : '') +(ci.vspace&& ci.vspace != "0" ? ' vspace = "' + ci.vspace + '"' : '') + '/>';if (ci['floatStyle'] == 'center') {str= '<p style="text-align: center">' + str + '</p>';}html.push(str);}else{for (var i = 0; ci = opt[i++];) {unhtmlData(ci);

// 修改:添加bootstrap的img-responsive样式以支持响应式图片

                    str = '<p ' + (ci['floatStyle'] == 'center' ? 'style="text-align: center" ' : '') + '><img

class="img-responsive"

 src="' + ci.src + '" ' +(ci.width? 'width="' + ci.width + '" ' : '') + (ci._src ? ' _src="' + ci._src + '" ' : '') +(ci.height? ' height="' + ci.height + '" ' : '') +' style="' + (ci['floatStyle'] && ci['floatStyle'] != 'center' ? 'float:' + ci['floatStyle'] + ';' : '') +(ci.border|| '') + '" ' +(ci.title? ' title="' + ci.title + '"' : '') + ' /></p>';html.push(str);}}……}……}
};

UE.plugin.register('simpleupload', function(){……functioncallback(){try{varlink, json, loader,body= (iframe.contentDocument ||iframe.contentWindow.document).body,result= body.innerText || body.textContent || '';json= (new Function("return " +result))();link= me.options.imageUrlPrefix +json.url;if(json.state == 'SUCCESS' &&json.url) {loader=me.document.getElementById(loadingId);loader.setAttribute('src', link);loader.setAttribute('_src', link);loader.setAttribute('title', json.title || '');loader.setAttribute('alt', json.original || '');loader.removeAttribute('id');domUtils.removeClasses(loader,'loadingclass');

// 修改:添加bootstrap的img-responsive样式以支持响应式图片 domUtils.addClass(loader, 'img-responsive');

}else{showErrorLoader&&showErrorLoader(json.state);}}catch(er){showErrorLoader&& showErrorLoader(me.getLang('simpleupload.loadError'));}form.reset();domUtils.un(iframe,'load', callback);}
……});

path-to-ueditor/dialogs/image/image.js

/*添加图片到列表界面上*/pushData:function(list) {……domUtils.on(img,'load', (function(image){return function(){_this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);}})(img));img.width= 113;img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );

// 修改:设置插入图片时引用七牛的原图(水印)样式

                    img.setAttribute('_src', urlPrefix +list[i].url_original);

// 修改:给图片添加titleicon.setAttribute('title', list[i].title);

domUtils.addClass(icon,'icon');item.appendChild(img);item.appendChild(icon);this.list.insertBefore(item, this.clearFloat);}}},

path-to-editor/dialogs/image/image.html

<divid="tabhead"class="tabhead"><spanclass="tab"data-content-id="remote"><varid="lang_tab_remote"></var></span><spanclass="tab focus"data-content-id="upload"><varid="lang_tab_upload"></var></span><spanclass="tab"data-content-id="online"><varid="lang_tab_online"></var></span>

<!-- 修改,关闭图片搜索界面 -->

            <!--<span class="tab" data-content-id="search"><var id="lang_tab_search"></var></span>--></div><divclass="alignBar">……</div><divid="tabbody"class="tabbody">……<!--搜索图片-->

<!-- 修改:关闭图片搜索界面 -->

<!--<div id="search" class="panel"><div class="searchBar"><input id="searchTxt" class="searchTxt text" type="text" /><select id="searchType" class="searchType"><option value="&s=4&z=0"></option><option value="&s=1&z=19"></option><option value="&s=2&z=0"></option><option value="&s=3&z=0"></option></select><input id="searchReset" type="button"  /><input id="searchBtn" type="button"  /></div><div id="searchList" class="searchList"><ul id="searchListUl"></ul></div></div>--></div>

path-to-ueditor/third-part/webuploader/webuploader*.js

由于七牛云在多线程上传时会时常报错,所以我们需要按照队列一个一个去上传就好了,上传调用的是百度自家的webuploader组件。我没有仔细研究ueditor到底调用的是哪一个文件,干脆就把所有文件中的

threads:3

改成

threads:1

8. 调试改错和已知bug:

调试改错这个过程是必须要经历的,有时候还是非常痛苦的,很多细小的忽视都会导致程序运行失败,认真并耐心就好了。

已知bug:

程序里使用unlink('.'.$file_path);这一句用来删除涂鸦临时保存在应用服务器上的文件,但是有时候会出现删不掉的情况。

9. GitHub:

我把完整的Demo上传到的我的GitHub仓库中,如需要完整源码可自行下载:

https://github.com/wandoubaba/tp-ue-qn-db

10. 效果演示:

11. 结束语

文本是我对前段时间所做研究的一个完整的复盘,但是即使是复盘,也并没有一下子就运行成功,而且在复盘时又调试出了新的bug,由此可见,对一些在项目中学习到的新技术进行适当的复盘重现,可以加深自己对技术的掌握,同时也能帮助到其他人,虽然多花了一些时间,但是我认为是值得的。

感谢你花时间读完了文章,如果你对需求有更好的解决方法,或者发现文中的错误和不足,也请你不吝赐教,互相交流以共同进步。

转载于:https://www.cnblogs.com/chenqiang001/p/10998776.html

在ThinkPHP框架(5.0.24)下引入Ueditor并实现向七牛云对象存储上传图片同时将图片信息保存到MySQL数据库,同时实现lazyload懒加载...相关推荐

  1. monolog mysql_Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的方法...

    本文实例讲述了Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的方法.分享给大家供大家参考,具体如下: Laravel中使用monolog_mysql将系统日志 ...

  2. monolog 存入mysql_Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的...

    本文实例讲述了Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的方法.,具体如下: Laravel中使用monolog_mysql将系统日志信息保存到mysql ...

  3. load方法引入本地html报错,分享基于plus.downloader的图片懒加载功能,支持本地缓存v1.1.0...

    今天试用了下hello mui上的图片懒加载功能,发现有些地方还无法满足我的需求,ajax动态加载的时候无法实现懒加载. 然后又看了下36kr的示例,因为代码关系实在太多了,耦合度比较高,遂自己动手写 ...

  4. mui ajax 懒加载,MUI框架运用中遇见问题总结

    H5在移动端的开发趋势化越来越大,相对App来说,H5优势有: 跨平台,兼容性强 开发速度快,成本较低 迭代周期短 技术成本低 但当我们在开始移动端的项目开发时,又愁着有什么样的好的UI框架能让我们减 ...

  5. vue的路由组件的引入以及路由组件懒加载和router-link

    App.vue <template><div><router-link to="/">首页</router-link><rou ...

  6. 深入学习SAP UI5框架代码系列之一:UI5 Module的懒加载机制

    本文是深入学习SAP UI5框架代码系列的第二篇文章. 系列目录 SAP UI5应用开发人员了解UI5框架代码的意义 UI5 module懒加载机制 UI5 控件渲染机制 HTML原生事件 VS SA ...

  7. webbrowser控件 加载为空白_深入学习SAP UI5框架代码系列之一:UI5 Module的懒加载机制...

    本文是深入学习SAP UI5框架代码系列的第二篇文章. 系列目录 SAP UI5应用开发人员了解UI5框架代码的意义 UI5 module懒加载机制 UI5 控件渲染机制 HTML原生事件 VS SA ...

  8. 移动端 懒加载、下拉刷新、上拉加载

    优势:提升性能 实现原理:图片是通过img的src属性,当对src赋值时,浏览器就会请求图片资源. 基于这个问题,我们可以利用标签的自定义属性(data-xxx),来保存图片的路径,当我们需要加载图片 ...

  9. 关于vue 动态引入(异步加载import和require)组件的方法和坑(按需懒加载组件,动态生成路由)babel-plugin-dynamic-import-node 优化编译速度

    前言: 最近在改造vue-cli 2.x + webpack2.x的项目时,由于之前路由是静态的,没有根据菜单权限动态生成前端路由.所以想对此进行改造,然后碰到了一些问题和坑,现在总结一下,避免以后继 ...

最新文章

  1. 伪距定位算法(matlab版)
  2. spring-retry----线程内重试
  3. 剑指offer-栈的压入、弹出序列
  4. SAP Gateway currency conversion utility
  5. 用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变
  6. xss挖掘思路分享_新手指南 | permeate靶场漏洞挖掘思路分享
  7. html5游戏制作入门系列教程(二)
  8. 用Dalvik指令集写个java类
  9. Huaman Gene Functions
  10. stata基本操作(二)
  11. ASP.NET2.0登陆控件的使用(常见的三种方法)
  12. 多学一点(十二)——使用extundelete恢复Linux下误删除文件
  13. List of X$ Tables and how the names are derived
  14. 艾肯声卡没有声音处理方法
  15. 3.6 OrCAD中元器件应该怎么进行镜像与翻转?
  16. 你一定要知道的四个程序员接外包的网站,悄悄把技术变现!
  17. mysql 100个标题_100个超强吸引人的标题
  18. 基于FPGA的数据采集、通讯和存储系统设计(即FPGA+RTL8211千兆以太网+SD卡存储+RTC+Uart+AD7606数模转换+电流放大采集等硬件设计及程序验证)
  19. [转]明朝出了个张居正 作者:秋风浩荡 -3
  20. 取消华为mate30 删除图片时手机弹出提示:“..检测xx删除了图片..“

热门文章

  1. Pandas知识点-索引和切片操作
  2. Auto Encoder再学习
  3. [机器学习-Sklearn]K-means(K均值)学习与总结
  4. html树 node节点定位,【Vue】element-ui Tree如何定位到一个节点,并高亮显示该节点?...
  5. python3能做什么_你都用 Python 来做什么?
  6. 角点检测--基于梯度的方法(Moravec角点检测、Harris角点检测、Shi-Tomasi角点检测)
  7. NetworkX学习笔记【持续更新】
  8. java并发的艺术_Java并发编程的艺术(一)
  9. 组态王调用mysql存储过程_组态王与数据库通讯
  10. ant react 上传_React实战之Ant Design—Upload上传_附件上传