這是我花了數日的時間才得以真正實現的一組需求。javascript
文章後面有完整Demo的GitHub連接。php
1、 需求描述html
1. 應用是基於ThinkPHP5開發的;前端
2. 服務器環境是LNMP,PHP版本是7.2,數據庫是MySQL5.6;java
3. 由用戶(包括管理員)上傳的圖片一類的媒體文件不能直接上傳到應用目錄中,而要上傳到單獨的對象存儲服務器上;mysql
4. 須要使用富文本編輯器,編輯器中須要上傳的圖片也都要保存到對象存儲服務器;jquery
5. 能夠對已上傳的圖片進行刪改查操做。git
2、 方案選型github
1. 框架:ThinkPHP 5.0.24(比較經常使用)web
2. 編輯器:ueditor1_4_3_3-utf8-php(停更前的最新版本)
3. 對象存儲:七牛雲(免費10G空間,官方SDK齊全)
4. 開發環境:windows+WAMPServer
3、 產品設計
本文要作的只是一個demo,其中只包含需求中說明的功能,而其餘相關的功能好比登陸、權限之類的就不在本文的研究範圍內。
對以上需求和方案進行分析,能夠總結出本文demo須要實現的具體功能以下:
1. bucket管理(七牛雲的存儲空間),增刪改查。
2. 圖片管理,上傳,增刪改查,上傳時將信息保存到數據庫中,查詢時從數據庫讀取數據並向雲存儲空間獲取圖片。
3. ueditor演示,插入本地圖片時將圖片上傳到雲存儲中並向數據庫插入記錄,插入遠程圖片時從數據庫讀取數據並向雲存儲獲取圖片,查看遠程列表時獲取縮略圖,插入時須要獲取大圖。
4、 實現過程
說明:本文將這個需求看成一個單獨的項目來重現實現過程,並且這個實現過程當中會對與本主題無關的但在開發過程當中須要用到的內容作忽略處理(好比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:
SET NAMES 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
<div class="box"> <div class="box-header"> <span> <a href="javascript:;" onclick="modal_show_iframe('添加存儲空間','{:url("index/picture/bucket_add")}',90)" class="btn btn-primary"><i class="fa fa-fw fa-plus-square"></i> 新增數據</a> </span> <span class="pull-right">共有數據:<strong>{$count}</strong> 條</span> </div> <div class="box-body" style="overflow-y: hidden;overflow-x: scroll;"> <table class="table table-bordered table-strited table-hover text-nowrap"> <thead> <tr> <th scope="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'} <tr title="{$vo.bucket_description}"> <td class="td-manage"> <a title="編輯" href="javascript:;" onclick="modal_show_iframe('編輯存儲空間','{:url("index/picture/bucket_edit",["name"=>$vo.bucket_name])}','')"><i class="fa fa-fw fa-pencil-square-o"></i></a> <a title="刪除" href="javascript:;" onclick="ajax_post_confirm('{:url("index/picture/do_bucket_delete")}',{name:'{$vo.bucket_name}'},'{$vo.bucket_name}','刪除');"><i class="fa fa-fw fa-trash-o"></i></a> </td> <td><span class="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> <div class="box-footer"> <div class="text-warning text-center">在電腦上操做會更舒服一些。</div> </div> </div>
(2) bucket_add.html
<form method="post" class="form-horizontal"> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>空間名稱:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="空間名稱,與雲上的bucket一致" id="bucket_name" name="bucket_name" rangelength="[1,50]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>空間域名:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="空間域名,http://.../形式,以/結尾" id="bucket_domain" name="bucket_domain" rangelength="[4,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3">描述:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="文字描述" id="bucket_description" name="bucket_description" maxlength="100"> </div> </div> <div class="form-group"> <label class="control-label col-sm-3">默認空間:</label> <div class="col-sm-9"> <input type="checkbox" id="bucket_default" name="bucket_default" /> 勾選爲默認,只能夠有1個默認空間 </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>縮略圖樣式:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="縮略圖樣式名" id="bucket_style_thumb" name="bucket_style_thumb" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>原圖樣式:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="原圖樣式名" id="bucket_style_original" name="bucket_style_original" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>原圖水印樣式:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="原圖加水印樣式名" id="bucket_style_water" name="bucket_style_water" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>適應寬度樣式:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="適應寬度樣式名" id="bucket_style_fixwidth" name="bucket_style_fixwidth" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>適應高度樣式:</label> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="適應高度樣式名" id="bucket_style_fixheight" name="bucket_style_fixheight" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <div class="col-sm-9 col-sm-offset-3"> <button type="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
<form method="post" class="form-horizontal"> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>空間名稱(只讀):</label> <div class="col-sm-9"> <input value="{$bucket.bucket_name}" type="text" class="form-control" id="bucket_name" name="bucket_name" readonly="true"> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>空間域名:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_domain}" type="text" class="form-control" placeholder="空間域名,http://.../形式,以/結尾" id="bucket_domain" name="bucket_domain" rangelength="[4,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3">描述:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_description}" type="text" class="form-control" placeholder="文字描述" id="bucket_description" name="bucket_description" maxlength="100"> </div> </div> <div class="form-group"> <label class="control-label col-sm-3">默認空間:</label> <div class="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> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>縮略圖樣式:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_style_thumb}" type="text" class="form-control" placeholder="縮略圖樣式名" id="bucket_style_thumb" name="bucket_style_thumb" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>原圖樣式:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_style_original}" type="text" class="form-control" placeholder="原圖樣式名" id="bucket_style_original" name="bucket_style_original" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>原圖水印樣式:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_style_water}" type="text" class="form-control" placeholder="原圖加水印樣式名" id="bucket_style_water" name="bucket_style_water" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>適應寬度樣式:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_style_fixwidth}" type="text" class="form-control" placeholder="適應寬度樣式名" id="bucket_style_fixwidth" name="bucket_style_fixwidth" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>適應高度樣式:</label> <div class="col-sm-9"> <input value="{$bucket.bucket_style_fixheight}" type="text" class="form-control" placeholder="適應高度樣式名" id="bucket_style_fixheight" name="bucket_style_fixheight" rangelength="[3,100]" required> </div> </div> <div class="form-group"> <div class="col-sm-9 col-sm-offset-3"> <button type="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
<div class="nav-tabs-custom"> <ul id="main-nav" class="nav nav-tabs"> <li class="header">空間 <i class="fa fa-arrow-right"></i> </li> {volist name="bucketlist" id="vo"} <li class="{if condition='$vo.bucket_default eq 1'}active{/if}"> <a href="#{$vo.bucket_name}" data-toggle="tab">{$vo.bucket_name}</a> </li> {/volist} </ul> <div id="main-nav-tabs" class="tab-content"> {volist name="bucketlist" id="vo"} <div class="tab-pane {eq name='vo.bucket_default' value='1'}active{/eq}" id="{$vo.bucket_name}"> <div class="row"> <div class="col-xs-3"> <a href="javascript:;" onclick="modal_show_iframe('上傳圖片','{:url("index/picture/add",["bucket"=>$vo.bucket_name])}','')" class="btn btn-primary"><i class="fa fa-fw fa-plus-square"></i> 上傳圖片</a> </div> </div> <div class="row mt-3"> {volist name="vo.child" id="vo_c" mod="6" empty="沒有圖片"} <div class="col-xs-6 col-md-4 col-lg-2"> <div class="panel {eq name='vo_c.picture_protected' value='1'}panel-danger{else/}panel-info{/eq}"> <div class="panel-heading ellipsis"> <span title="{$vo_c.picture_name}"> {$vo_c.picture_name} </span> </div> <div class="panel-body"> <a href="{$vo.bucket_domain}{$vo_c.picture_key}-{$vo.bucket_style_water}" data-lightbox="qiniu-image"> <img class="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> <div class="panel-footer ellipsis"> <span title="{$vo_c.picture_description}"> {$vo_c.picture_description} </span><br/> <span title="{$vo_c.create_time}"> {$vo_c.create_time} </span><br/> <span title="{$vo_c.update_time}"> {$vo_c.update_time} </span><br/> <span class="pull-right"> <a href="javascript:;" onclick="modal_show_iframe('編輯圖片','{:url("index/picture/edit",["id"=>$vo_c.picture_id])}','')" title="編輯"><i class="fa fa-edit fa-fw"></i></a> <a href="javascript:;" onclick="ajax_post_confirm('{:url("index/picture/do_picture_delete")}',{id:'{$vo_c.picture_id}'},'{$vo_c.picture_name}','刪除');" title="刪除"><i class="fa fa-trash fa-fw"></i></a> </span> </div> </div> </div> {eq name="mod" value="5"} </div><div class="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
<form method="post" enctype="multipart/form-data" class="form-horizontal"> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>選擇空間</label> <div class="col-sm-9"> <select name="bucket_name" class="form-control"> {volist name="bucketlist" id="vo"} <option value="{$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> <div class="form-group"> <label class="control-label col-sm-3"><span class="text-red">*</span>圖片文件</label> <div class="col-sm-9"> <input type="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> <div class="form-group"> <lable class="control-label col-sm-3">圖片標題</lable> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="給圖片設定一個標題,空白默認文件名" id="picture_name" name="picture_name" /> </div> </div> <div class="form-group"> <lable class="control-label col-sm-3">圖片描述</lable> <div class="col-sm-9"> <input type="text" class="form-control" placeholder="給圖片編輯一段描述" id="picture_description" name="picture_description" /> </div> </div> <div class="form-group"> <lable class="control-label col-sm-3">權限保護</lable> <div class="col-sm-9"> <label> <input type="checkbox" id="picture_protected" name="picture_protected" /> 勾選表示設置權限保護(輕易不要勾選) </label> </div> </div> <div class="form-group"> <div class="col-sm-9 col-sm-offset-3"> <button type="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(); } }); }); function upload() { var formData = new FormData(); 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
<form method="post" enctype="multipart/form-data" class="form-horizontal"> <div class="form-group hide"> <lable class="control-label col-sm-3">圖片ID<span class="text-red">*</span></lable> <div class="col-sm-9"> <input value="{$picture.picture_id}" type="text" class="form-control" id="picture_id" name="picture_id" readonly="true" required /> </div> </div> <div class="form-group"> <lable class="control-label col-sm-3">圖片標題<span class="text-red">*</span></lable> <div class="col-sm-9"> <input value="{$picture.picture_name}" type="text" class="form-control" placeholder="給圖片設定一個標題" id="picture_name" name="picture_name" required /> </div> </div> <div class="form-group"> <lable class="control-label col-sm-3">圖片描述</lable> <div class="col-sm-9"> <input value="{$picture.picture_description}" type="text" class="form-control" placeholder="給圖片編輯一段描述" id="picture_description" name="picture_description" /> </div> </div> <div class="form-group"> <lable class="control-label col-sm-3">權限保護</lable> <div class="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> <div class="form-group"> <div class="col-sm-9 col-sm-offset-3"> <button type="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 extends Base { public function index() { $this->view->assign('pagetitle', '圖片管理'); // 加載bucket列表 $bucketlist = BucketModel::all(function($query) { $query->order(['bucket_default'=>'desc', 'bucket_name'=>'asc']); }); // 加載bucket裏的圖片 $picturelist; // 遍歷bucket foreach($bucketlist as $n=>$bucket) { $picture = new PictureModel; $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 function add() { $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 function edit() { $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 function do_picture_edit() { $res = new Res; $res->data = input(); $res->data['picture_protected'] = input('?picture_protected') ? 1 : 0; try { $picture = new PictureModel; $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 function do_picture_add() { $res = new Res; $picture_file = request()->file('picture_file'); $picture = new PictureModel; $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 function do_picture_delete() { $res = new Res; 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 function bucket() { $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 function bucket_add() { $this->view->assign('pagetitle', '添加存儲空間'); return $this->view->fetch('picture/bucket_add'); } /** * 執行添加空間操做 * @return [type] [description] */ public function do_bucket_add() { $res = new Res; $res->data = input(); $res->data['bucket_default'] = input('?bucket_default') ? 1 : 0; $bucket = new BucketModel; $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 function bucket_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 function do_bucket_edit() { $res = new Res; $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 = new BucketModel; 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 function do_bucket_delete() { $res = new Res; $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特性。
class QiniuService { /** * 向七牛雲存儲獲取指定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 function generate_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 = new Res; 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 = new Res; $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 = new Res; 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 extends CommonBase { /** * 從數據庫中找到第1個默認bucket * @return [type] [description] */ private function default_bucket() { $bucket = new Bucket; // 向數據庫查詢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 = new Res; 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 = new QiniuService; 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 = new Res; $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 = new Res; $picture = new Picture; $picture->bucket_name = $this->default_bucket()->bucket_name; $picture->picture_name = pathinfo($file_path, PATHINFO_BASENAME); $picture->picture_description = $picture->picture_name; try { $qservice = new QiniuService; $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 = new Res; $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); // 返回的是七牛雲上的url return $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 extends ApiBase { private $uploadfolder='/upload/'; //上傳地址 private $scrawlfolder='/upload/_scrawl/'; //塗鴉保存地址 private $catchfolder='/upload/_catch/'; //遠程抓取地址 private $configpath='/static/lib/ueditor/utf8-php/php/config.json'; //先後端通訊相關的配置 private $config; public function index(){ $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 = new Picture; // demo中暫時關閉關於admin的處理 // $picture->admin_id = Session::has('admin_infor')?Session::get('admin_infor')->admin_id:''; $pservice = new PictureService; $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 = new PictureService; // 在服務器保存圖片文件 $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 = '<imgclass="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" ' : '') + '><imgclass="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 (){ ……function callback(){ try{ var link, 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);
// 修改:給圖片添加title
icon.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
<div id="tabhead" class="tabhead"> <span class="tab" data-content-id="remote"><var id="lang_tab_remote"></var></span> <span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span> <span class="tab" data-content-id="online"><var id="lang_tab_online"></var></span><!-- 修改,關閉圖片搜索界面 -->
<!-- <span class="tab" data-content-id="search"><var id="lang_tab_search"></var></span> --> </div> <div class="alignBar"> …… </div> <div id="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,因而可知,對一些在項目中學習到的新技術進行適當的覆盤重現,能夠加深本身對技術的掌握,同時也能幫助到其餘人,雖然多花了一些時間,可是我認爲是值得的。
感謝你花時間讀完了文章,若是你對需求有更好的解決方法,或者發現文中的錯誤和不足,也請你不吝賜教,互相交流以共同進步。