Browse Source

增加阿里云上传支持

Anyon 5 years ago
parent
commit
8c12c83ba4

+ 27 - 0
app/admin/controller/Config.php

@@ -32,6 +32,33 @@ class Config extends Controller
     protected $table = 'SystemConfig';
 
     /**
+     * 阿里数据中心
+     * @var array
+     */
+    protected $points = [
+        'oss-cn-hangzhou.aliyuncs.com'    => '华东 1(杭州)',
+        'oss-cn-shanghai.aliyuncs.com'    => '华东 2(上海)',
+        'oss-cn-qingdao.aliyuncs.com'     => '华北 1(青岛)',
+        'oss-cn-beijing.aliyuncs.com'     => '华北 2(北京)',
+        'oss-cn-zhangjiakou.aliyuncs.com' => '华北 3(张家口)',
+        'oss-cn-huhehaote.aliyuncs.com'   => '华北 5(呼和浩特)',
+        'oss-cn-shenzhen.aliyuncs.com'    => '华南 1(深圳)',
+        'oss-cn-chengdu.aliyuncs.com'     => '西南 1(成都)',
+        'oss-cn-hongkong.aliyuncs.com'    => '中国(香港)',
+        'oss-us-west-1.aliyuncs.com'      => '美国西部 1(硅谷)',
+        'oss-us-east-1.aliyuncs.com'      => '美国东部 1(弗吉尼亚)',
+        'oss-ap-southeast-1.aliyuncs.com' => '亚太东南 1(新加坡)',
+        'oss-ap-southeast-2.aliyuncs.com' => '亚太东南 2(悉尼)',
+        'oss-ap-southeast-3.aliyuncs.com' => '亚太东南 3(吉隆坡)',
+        'oss-ap-southeast-5.aliyuncs.com' => '亚太东南 5(雅加达)',
+        'oss-ap-northeast-1.aliyuncs.com' => '亚太东北 1(日本)',
+        'oss-ap-south-1.aliyuncs.com'     => '亚太南部 1(孟买)',
+        'oss-eu-central-1.aliyuncs.com'   => '欧洲中部 1(法兰克福)',
+        'oss-eu-west-1.aliyuncs.com'      => '英国(伦敦)',
+        'oss-me-east-1.aliyuncs.com'      => '中东东部 1(迪拜)'
+    ];
+
+    /**
      * 系统参数配置
      * @auth true
      * @menu true

+ 38 - 29
app/admin/controller/api/Upload.php

@@ -17,6 +17,9 @@ namespace app\admin\controller\api;
 
 use think\admin\Controller;
 use think\admin\Storage;
+use think\admin\storage\AliossStorage;
+use think\admin\storage\LocalStorage;
+use think\admin\storage\QiniuStorage;
 
 /**
  * 文件上传接口
@@ -28,7 +31,6 @@ class Upload extends Controller
 
     /**
      * 上传安全检查
-     * @login true
      * @throws \think\Exception
      * @throws \think\db\exception\DataNotFoundException
      * @throws \think\db\exception\DbException
@@ -36,18 +38,44 @@ class Upload extends Controller
      */
     public function check()
     {
-        $diff1 = explode(',', strtolower(input('exts', '')));
-        $diff2 = explode(',', strtolower(sysconf('storage.allow_exts')));
-        $exts = array_intersect($diff1, $diff2);
-        $this->success('获取文件上传参数', [
-            'type' => $this->getType(), 'data' => $this->getData(),
-            'exts' => join('|', $exts), 'mime' => Storage::mime($exts),
-        ]);
+        $exts = array_intersect(explode(',', input('exts', '')), explode(',', sysconf('storage.allow_exts')));
+        $this->success('获取文件上传参数', ['exts' => join('|', $exts), 'mime' => Storage::mime($exts)]);
+    }
+
+    /**
+     * 检查文件上传已经上传
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function state()
+    {
+        $data = ['uptype' => $this->getType(), 'xkey' => input('xkey')];
+        if ($info = Storage::instance($data['uptype'])->info($data['xkey'])) {
+            $data['url'] = $info['url'];
+            $data['pathinfo'] = $info['file'];
+            $this->success('文件已经上传', $data, 200);
+        } elseif ('local' === $data['uptype']) {
+            $data['url'] = LocalStorage::instance()->url($data['xkey']);
+            $data['server'] = LocalStorage::instance()->upload();
+        } elseif ('qiniu' === $data['uptype']) {
+            $data['url'] = QiniuStorage::instance()->url($data['xkey']);
+            $data['token'] = QiniuStorage::instance()->buildUploadToken($data['xkey']);
+            $data['server'] = QiniuStorage::instance()->upload();
+        } elseif ('alioss' === $data['uptype']) {
+            $token = AliossStorage::instance()->buildUploadToken($data['xkey']);
+            $data['server'] = AliossStorage::instance()->upload();
+            $data['url'] = $token['siteurl'];
+            $data['policy'] = $token['policy'];
+            $data['signature'] = $token['signature'];
+            $data['OSSAccessKeyId'] = $token['keyid'];
+        }
+        $this->success('获取上传参数', $data, 404);
     }
 
     /**
      * 文件上传入口
-     * @login true
      * @return \think\response\Json
      * @throws \think\Exception
      * @throws \think\db\exception\DataNotFoundException
@@ -77,25 +105,6 @@ class Upload extends Controller
     }
 
     /**
-     * 获取文件上传参数
-     * @return array
-     * @throws \think\Exception
-     * @throws \think\db\exception\DataNotFoundException
-     * @throws \think\db\exception\DbException
-     * @throws \think\db\exception\ModelNotFoundException
-     */
-    private function getData()
-    {
-        if ($this->getType() === 'qiniu') {
-            $file = Storage::instance('qiniu');
-            return ['url' => $file->upload(), 'token' => $file->buildUploadToken(), 'uptype' => $this->getType()];
-        } else {
-            $file = Storage::instance('local');
-            return ['url' => $file->upload(), 'token' => uniqid('local_upload_'), 'uptype' => $this->getType()];
-        }
-    }
-
-    /**
      * 获取文件上传方式
      * @return string
      * @throws \think\db\exception\DataNotFoundException
@@ -105,7 +114,7 @@ class Upload extends Controller
     private function getType()
     {
         $this->uptype = input('uptype');
-        if (!in_array($this->uptype, ['local', 'qiniu'])) {
+        if (!in_array($this->uptype, ['local', 'qiniu', 'alioss'])) {
             $this->uptype = sysconf('storage.type');
         }
         return $this->uptype;

+ 1 - 1
app/admin/view/config/index.html

@@ -12,7 +12,7 @@
 
 <div class="think-box-shadow margin-bottom-15">
     <span class="color-green font-w7 text-middle">文件存储方式:</span>
-    {foreach ['local'=>'本地服务器存储','qiniu'=>'七牛云对象存储'] as $k => $v}
+    {foreach ['local'=>'本地服务器存储','qiniu'=>'七牛云对象存储','alioss'=>'阿里云OSS存储'] as $k => $v}
     {if sysconf('storage.type') eq $k}
     {if auth('storage')}<a data-title="配置{$v}" data-modal="{:url('storage')}?type={$k}" class="layui-btn layui-btn-sm">{$v}</a>{else}<a class="layui-btn layui-btn-sm">{$v}</a>{/if}
     {else}

+ 105 - 0
app/admin/view/config/storage-alioss.html

@@ -0,0 +1,105 @@
+<form onsubmit="return false" data-auto="true" action="{:url()}" method="post" class='layui-form layui-card' autocomplete="off">
+    <div class="layui-card-body">
+
+        <div class="color-text margin-left-40 margin-bottom-20 layui-code" style="border-left-width:1px;background:none">
+            <p class="margin-bottom-5 font-w7">文件将上传到阿里云OSS空间,需要配置OSS公开访问及跨域策略(目前已实现自动创建空间及配置访问策略)。</p>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.allow_exts">
+                <span class="color-green font-w7">AllowExts</span><br><span class="nowrap color-desc">允许类型</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.allow_exts" type="text" name="storage.allow_exts" required value="{:sysconf('storage.allow_exts')}" placeholder="请输入系统文件上传后缀" class="layui-input">
+                <p class="help-block">设置系统允许上传文件的后缀,多个以英文逗号隔开。如:png,jpg,rar,doc</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label label-required">
+                <span class="color-green font-w7">Protocol</span><br><span class="nowrap color-desc">访问协议</span>
+            </label>
+            <div class="layui-input-block">
+                {foreach ['http','https','auto'] as $protocol}
+                <label class="think-radio">
+                    {if sysconf('storage.alioss_http_protocol') eq $protocol}
+                    <input checked type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$protocol}
+                    {else}
+                    <input type="radio" name="storage.alioss_http_protocol" value="{$protocol}" lay-ignore> {$protocol}
+                    {/if}
+                </label>
+                {/foreach}
+                <p class="help-block">阿里云OSS存储访问协议,其中 https 需要配置证书才能使用,auto 为相对协议。</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">
+                <span class="color-green font-w7">Point</span><br><span class="nowrap color-desc label-required">存储区域</span>
+            </label>
+            <div class="layui-input-block">
+                <select class="layui-select" name="storage.alioss_point" lay-search>
+                    {foreach $points as $point => $title}
+                    {if sysconf('storage.alioss_point') eq $point}
+                    <option selected value="{$point}">{$title} {$point}</option>
+                    {else}
+                    <option value="{$point}">{$title} {$point}</option>
+                    {/if}
+                    {/foreach}
+                </select>
+                <p class="help-block">阿里云OSS存储空间所在区域,需要严格对应储存所在区域才能上传文件。</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.alioss_bucket">
+                <span class="color-green font-w7">Bucket</span><br><span class="nowrap color-desc">空间名称</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.alioss_bucket" type="text" name="storage.alioss_bucket" required value="{:sysconf('storage.alioss_bucket')}" placeholder="请输入阿里云OSS存储 Bucket (空间名称)" class="layui-input">
+                <p class="help-block">填写OSS存储空间名称,如:think-admin-oss(需要是全区唯一的值,不存在时会自动创建)</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.alioss_http_domain">
+                <span class="color-green font-w7">Domain</span><br><span class="nowrap color-desc">访问域名</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.alioss_http_domain" type="text" name="storage.alioss_http_domain" required value="{:sysconf('storage.alioss_http_domain')}" placeholder="请输入阿里云OSS存储 Domain (访问域名)" class="layui-input">
+                <p class="help-block">填写OSS存储外部访问域名,如:static.ctolog.com</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.alioss_access_key">
+                <span class="color-green font-w7">AccessKey</span><br><span class="nowrap color-desc">访问密钥</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.alioss_access_key" type="text" name="storage.alioss_access_key" required value="{:sysconf('storage.alioss_access_key')}" placeholder="请输入阿里云OSS存储 AccessKey (访问密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到访问密钥。</p>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label" for="storage.alioss_secret_key">
+                <span class="color-green font-w7">SecretKey</span><br><span class="nowrap color-desc">安全密钥</span>
+            </label>
+            <div class="layui-input-block">
+                <input id="storage.alioss_secret_key" type="text" name="storage.alioss_secret_key" required value="{:sysconf('storage.alioss_secret_key')}" maxlength="43" placeholder="请输入阿里云OSS存储 SecretKey (安全密钥)" class="layui-input">
+                <p class="help-block">可以在 [ 阿里云 > 个人中心 ] 设置并获取到安全密钥。</p>
+            </div>
+        </div>
+
+        <div class="hr-line-dashed margin-left-40"></div>
+        <input type="hidden" name="storage.type" value="alioss">
+
+        <div class="layui-form-item text-center padding-left-40">
+            <button class="layui-btn" type="submit">保存配置</button>
+            <button class="layui-btn layui-btn-danger" type='button' data-confirm="确定要取消修改吗?" data-close>取消修改</button>
+        </div>
+
+        <script>form.render()</script>
+
+    </div>
+</form>

+ 4 - 4
composer.lock

@@ -909,12 +909,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/zoujingli/ThinkLibrary.git",
-                "reference": "d70ad59df16eebb63e7d8a5596cc943e90d928c9"
+                "reference": "5df24247da39c5b68c3bce2153fe5449f1caabd1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/d70ad59df16eebb63e7d8a5596cc943e90d928c9",
-                "reference": "d70ad59df16eebb63e7d8a5596cc943e90d928c9",
+                "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/5df24247da39c5b68c3bce2153fe5449f1caabd1",
+                "reference": "5df24247da39c5b68c3bce2153fe5449f1caabd1",
                 "shasum": "",
                 "mirrors": [
                     {
@@ -958,7 +958,7 @@
             ],
             "description": "ThinkPHP v6.0 Development Library",
             "homepage": "http://framework.thinkadmin.top",
-            "time": "2019-12-17T10:36:55+00:00"
+            "time": "2019-12-20T03:07:55+00:00"
         },
         {
             "name": "zoujingli/wechat-developer",

+ 1 - 1
config/database.php

@@ -28,7 +28,7 @@ return [
             // 数据库类型
             'type'              => 'mysql',
             // 服务器地址
-            'hostname'          => '127.0.0.1',
+            'hostname'          => 'server.cuci.cc',
             // 数据库名
             'database'          => 'admin_v6',
             // 用户名

+ 111 - 22
public/static/plugs/jquery/uploader.js

@@ -1,43 +1,132 @@
-define(function () {
-    return function (element, InitHandler, UploadedHandler, CompleteHandler) {
+define(['md5'], function (SparkMD5) {
+    return function (element, InitHandler, UploadedHandler) {
         var exts = $(element).data('type') || '*';
         var uptype = $(element).attr('data-uptype') || '';
+        var multiple = $(element).attr('data-multiple') > 0;
 
         // 检查可以上传的文件后缀
-        $.form.load('?s=admin/api.upload/check', {exts: exts, uptype: uptype}, 'post', function (ret, options) {
-            options = {url: ret.data.data.url, exts: ret.data.exts, acceptMime: ret.data.mime, data: ret.data.data};
-            if (exts.indexOf('*') > -1) delete options.exts, delete options.acceptMime;
-            return renderUploader(options), false;
-        }, false, false, 0);
+        jQuery.ajax('?s=admin/api.upload/check', {
+            method: 'POST', data: {exts: exts, uptype: uptype}, success: function (ret, options) {
+                options = {exts: ret.data.exts, acceptMime: ret.data.mime, data: {}};
+                if (exts.indexOf('*') > -1) delete options.exts, delete options.acceptMime;
+                renderUploader(options)
+            }
+        });
 
         // 初始化上传组件
-        function renderUploader(options, headers) {
-            this.options = {
-                proindex: 0,
-                elem: element,
-                headers: headers || {},
-                multiple: $(element).attr('data-multiple') > 0,
+        function renderUploader(options, headers, uploader) {
+            uploader = layui.upload.render({
+                idx: 0, urls: {}, auto: false, elem: element,
+                headers: headers || {}, multiple: multiple,
+                exts: options.exts, acceptMime: options.acceptMime,
+                choose: function (object, files) {
+                    files = object.pushFile();
+                    for (var index in files) {
+                        md5file(files[index]).then(function (file) {
+                            jQuery.ajax("?s=admin/api.upload/state", {
+                                data: {xkey: file.xkey, uptype: uptype}, method: 'POST', success: function (ret) {
+                                    if (ret.code === 404) {
+                                        uploader.config.url = ret.data.server;
+                                        uploader.config.urls[index] = ret.data.url;
+                                        if (ret.data.uptype === 'qiniu') {
+                                            uploader.config.data.key = ret.data.xkey;
+                                            uploader.config.data.token = ret.data.token;
+                                        }
+                                        if (ret.data.uptype === 'alioss') {
+                                            uploader.config.data.key = ret.data.xkey;
+                                            uploader.config.data.policy = ret.data.policy;
+                                            uploader.config.data.signature = ret.data.signature;
+                                            uploader.config.data.OSSAccessKeyId = ret.data.OSSAccessKeyId;
+                                            uploader.config.data.success_action_status = 200;
+                                        }
+                                        object.upload(index, file);
+                                    } else if (ret.code === 200) {
+                                        UploadedHandler(ret.data.url, file.xkey);
+                                    } else {
+                                        $.msg.error(ret.info || ret.error.message || '文件上传出错!');
+                                    }
+                                }
+                            });
+                        });
+                        delete files[index];
+                    }
+                },
                 before: function () {
-                    this.proindex = $.msg.loading('上传进度 <span data-upload-progress>0%</span>');
+                    this.idx = $.msg.loading('上传进度 <span data-upload-progress>0%</span>');
                 },
                 progress: function (n) {
                     $('[data-upload-progress]').html(n + '%');
                 },
-                done: function (ret) {
-                    this.multiple || $.msg.close(this.proindex);
+                done: function (ret, index) {
+                    this.multiple || $.msg.close(this.idx);
+                    if (typeof ret.uploaded === 'undefined' && this.urls[index]) {
+                        ret = {uploaded: true, url: this.urls[index]};
+                    }
                     if (ret.uploaded) {
-                        if (typeof UploadedHandler === 'function') UploadedHandler(ret.url);
-                        else $('[name="' + ($(element).data('field') || 'file') + '"]').val(ret.url).trigger('change');
+                        if (typeof UploadedHandler === 'function') {
+                            UploadedHandler(ret.url);
+                        } else {
+                            $('[name="' + ($(element).data('field') || 'file') + '"]').val(ret.url).trigger('change');
+                        }
                     } else {
                         $.msg.error(ret.info || ret.error.message || '文件上传出错!');
                     }
                 },
                 allDone: function () {
-                    $.msg.close(this.proindex), $(element).html($(element).data('html'));
-                    if (typeof CompleteHandler === 'function') CompleteHandler();
+                    $.msg.close(this.idx), $(element).html($(element).data('html'));
                 }
-            };
-            layui.upload.render($.extend(this.options, options));
+            });
         };
     };
+
+    function md5file(file) {
+        var deferred = jQuery.Deferred();
+        file.xext = file.name.indexOf('.') > -1 ? file.name.split('.').pop() : 'tmp';
+
+        if (!window.FileReader) return jQuery.when((function (date, chars) {
+            date = new Date(), chars = 'abcdefhijkmnprstwxyz0123456789';
+            this.xmd5 = '' + date.getFullYear() + (date.getMonth() + 1) + date.getDay() + date.getHours() + date.getMinutes() + date.getSeconds();
+            while (this.xmd5.length < 32) this.xmd5 += chars.charAt(Math.floor(Math.random() * chars.length));
+            setFileXdata(file, this.xmd5);
+            deferred.resolve(file, file.xmd5, file.xkey);
+            return deferred;
+        }).call(this));
+
+        var spark = new SparkMD5.ArrayBuffer();
+        var slice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+        file.chunk_idx = 0;
+        file.chunk_size = 2097152;
+        file.chunk_total = Math.ceil(this.size / this.chunk_size);
+        return jQuery.when(loadNextChunk(file));
+
+        function setFileXdata(file, xmd5) {
+            file.xmd5 = xmd5;
+            file.xkey = file.xmd5.substr(0, 16) + '/' + file.xmd5.substr(16, 16) + '.' + file.xext;
+            delete file.chunk_idx;
+            delete file.chunk_size;
+            delete file.chunk_total;
+            return file;
+        }
+
+        function loadNextChunk(file) {
+            this.reader = new FileReader();
+            this.reader.onload = function (e) {
+                spark.append(e.target.result);
+                if (++file.chunk_idx < file.chunk_total) {
+                    loadNextChunk(file);
+                } else {
+                    setFileXdata(file, spark.end());
+                    deferred.resolve(file, file.xmd5, file.xkey);
+                }
+            };
+            this.reader.onerror = function () {
+                deferred.reject();
+            };
+            this.start = file.chunk_idx * file.chunk_size;
+            this.loaded = ((this.start + file.chunk_size) >= file.size) ? file.size : this.start + file.chunk_size;
+            this.reader.readAsArrayBuffer(slice.call(file, this.start, this.loaded));
+            deferred.notify(file, (this.loaded / file.size * 100).toFixed(2));
+            return deferred;
+        }
+    }
 });

File diff suppressed because it is too large
+ 0 - 0
public/static/plugs/layui/layui.all.js


+ 1 - 0
vendor/composer/autoload_classmap.php

@@ -282,6 +282,7 @@ return array(
     'think\\admin\\service\\QueueService' => $vendorDir . '/zoujingli/think-library/src/service/QueueService.php',
     'think\\admin\\service\\SystemService' => $vendorDir . '/zoujingli/think-library/src/service/SystemService.php',
     'think\\admin\\service\\TokenService' => $vendorDir . '/zoujingli/think-library/src/service/TokenService.php',
+    'think\\admin\\storage\\AliossStorage' => $vendorDir . '/zoujingli/think-library/src/storage/AliossStorage.php',
     'think\\admin\\storage\\LocalStorage' => $vendorDir . '/zoujingli/think-library/src/storage/LocalStorage.php',
     'think\\admin\\storage\\QiniuStorage' => $vendorDir . '/zoujingli/think-library/src/storage/QiniuStorage.php',
     'think\\app\\MultiApp' => $vendorDir . '/topthink/think-multi-app/src/MultiApp.php',

+ 1 - 0
vendor/composer/autoload_static.php

@@ -422,6 +422,7 @@ class ComposerStaticInitada0e677dd8f1307ba83d0cf07626e6c
         'think\\admin\\service\\QueueService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/QueueService.php',
         'think\\admin\\service\\SystemService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/SystemService.php',
         'think\\admin\\service\\TokenService' => __DIR__ . '/..' . '/zoujingli/think-library/src/service/TokenService.php',
+        'think\\admin\\storage\\AliossStorage' => __DIR__ . '/..' . '/zoujingli/think-library/src/storage/AliossStorage.php',
         'think\\admin\\storage\\LocalStorage' => __DIR__ . '/..' . '/zoujingli/think-library/src/storage/LocalStorage.php',
         'think\\admin\\storage\\QiniuStorage' => __DIR__ . '/..' . '/zoujingli/think-library/src/storage/QiniuStorage.php',
         'think\\app\\MultiApp' => __DIR__ . '/..' . '/topthink/think-multi-app/src/MultiApp.php',

+ 4 - 4
vendor/composer/installed.json

@@ -935,12 +935,12 @@
         "source": {
             "type": "git",
             "url": "https://github.com/zoujingli/ThinkLibrary.git",
-            "reference": "d70ad59df16eebb63e7d8a5596cc943e90d928c9"
+            "reference": "5df24247da39c5b68c3bce2153fe5449f1caabd1"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/d70ad59df16eebb63e7d8a5596cc943e90d928c9",
-            "reference": "d70ad59df16eebb63e7d8a5596cc943e90d928c9",
+            "url": "https://api.github.com/repos/zoujingli/ThinkLibrary/zipball/5df24247da39c5b68c3bce2153fe5449f1caabd1",
+            "reference": "5df24247da39c5b68c3bce2153fe5449f1caabd1",
             "shasum": "",
             "mirrors": [
                 {
@@ -956,7 +956,7 @@
             "ext-json": "*",
             "topthink/framework": "^6.0"
         },
-        "time": "2019-12-17T10:36:55+00:00",
+        "time": "2019-12-20T03:07:55+00:00",
         "type": "library",
         "extra": {
             "think": {

+ 1 - 1
vendor/services.php

@@ -1,5 +1,5 @@
 <?php 
-// This file is automatically generated at:2019-12-17 18:43:07
+// This file is automatically generated at:2019-12-20 11:17:58
 declare (strict_types = 1);
 return array (
   0 => 'think\\app\\Service',

+ 2 - 2
vendor/zoujingli/think-library/src/Storage.php

@@ -150,8 +150,8 @@ abstract class Storage
     public static function mime($exts, $mime = []): string
     {
         $mimes = self::mimes();
-        foreach (is_string($exts) ? explode(',', $exts) : $exts as $e) {
-            $mime[] = isset($mimes[strtolower($e)]) ? $mimes[strtolower($e)] : 'application/octet-stream';
+        foreach (is_string($exts) ? explode(',', $exts) : $exts as $ext) {
+            $mime[] = isset($mimes[strtolower($ext)]) ? $mimes[strtolower($ext)] : 'application/octet-stream';
         }
         return join(',', array_unique($mime));
     }

+ 22 - 15
vendor/zoujingli/think-library/src/extend/HttpExtend.php

@@ -24,43 +24,43 @@ class HttpExtend
 {
     /**
      * 以get模拟网络请求
-     * @param string $url HTTP请求地址
+     * @param string $location HTTP请求地址
      * @param array|string $query GET请求参数
      * @param array $options CURL请求参数
      * @return boolean|string
      */
-    public static function get($url, $query = [], $options = [])
+    public static function get($location, $query = [], $options = [])
     {
         $options['query'] = $query;
-        return self::request('get', $url, $options);
+        return self::request('get', $location, $options);
     }
 
     /**
      * 以post模拟网络请求
-     * @param string $url HTTP请求地址
+     * @param string $location HTTP请求地址
      * @param array|string $data POST请求数据
      * @param array $options CURL请求参数
      * @return boolean|string
      */
-    public static function post($url, $data = [], $options = [])
+    public static function post($location, $data = [], $options = [])
     {
         $options['data'] = $data;
-        return self::request('post', $url, $options);
+        return self::request('post', $location, $options);
     }
 
     /**
      * CURL模拟网络请求
      * @param string $method 请求方法
-     * @param string $url 请求方法
-     * @param array $options 请求参数[headers,data]
+     * @param string $location 请求地址
+     * @param array $options 请求参数[headers,data,cookie,cookie_file,timeout,returnHeader]
      * @return boolean|string
      */
-    public static function request($method, $url, $options = [])
+    public static function request($method, $location, $options = [])
     {
         $curl = curl_init();
         // GET 参数设置
         if (!empty($options['query'])) {
-            $url .= (stripos($url, '?') !== false ? '&' : '?') . http_build_query($options['query']);
+            $location .= (stripos($location, '?') !== false ? '&' : '?') . http_build_query($options['query']);
         }
         // 浏览器代理设置
         curl_setopt($curl, CURLOPT_USERAGENT, self::getUserAgent());
@@ -76,9 +76,11 @@ class HttpExtend
             curl_setopt($curl, CURLOPT_COOKIEJAR, $options['cookie_file']);
             curl_setopt($curl, CURLOPT_COOKIEFILE, $options['cookie_file']);
         }
-        // POST 数据设置
-        if (strtolower($method) === 'post') {
-            curl_setopt($curl, CURLOPT_POST, true);
+        // 设置请求方式
+        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
+        if (strtolower($method) === 'head') {
+            curl_setopt($curl, CURLOPT_NOBODY, 1);
+        } elseif (isset($options['data'])) {
             curl_setopt($curl, CURLOPT_POSTFIELDS, self::buildQueryData($options['data']));
         }
         // 请求超时设置
@@ -87,8 +89,13 @@ class HttpExtend
         } else {
             curl_setopt($curl, CURLOPT_TIMEOUT, 60);
         }
-        curl_setopt($curl, CURLOPT_URL, $url);
-        curl_setopt($curl, CURLOPT_HEADER, false);
+        if (empty($options['returnHeader'])) {
+            curl_setopt($curl, CURLOPT_HEADER, false);
+        } else {
+            curl_setopt($curl, CURLOPT_HEADER, true);
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        }
+        curl_setopt($curl, CURLOPT_URL, $location);
         curl_setopt($curl, CURLOPT_AUTOREFERER, true);
         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
         curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

+ 262 - 0
vendor/zoujingli/think-library/src/storage/AliossStorage.php

@@ -0,0 +1,262 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | Library for ThinkAdmin
+// +----------------------------------------------------------------------
+// | 版权所有 2014~2019 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
+// +----------------------------------------------------------------------
+// | 官方网站: http://demo.thinkadmin.top
+// +----------------------------------------------------------------------
+// | 开源协议 ( https://mit-license.org )
+// +----------------------------------------------------------------------
+// | gitee 仓库地址 :https://gitee.com/zoujingli/ThinkLibrary
+// | github 仓库地址 :https://github.com/zoujingli/ThinkLibrary
+// +----------------------------------------------------------------------
+
+namespace think\admin\storage;
+
+use think\admin\extend\HttpExtend;
+use think\admin\Storage;
+
+/**
+ * 阿里云OSS存储支持
+ * Class AliossStorage
+ * @package think\admin\storage
+ */
+class AliossStorage extends Storage
+{
+    /**
+     * 数据中心
+     * @var string
+     */
+    private $point;
+
+    /**
+     * 存储空间名称
+     * @var string
+     */
+    private $bucket;
+
+    /**
+     * 绑定访问域名
+     * @var string
+     */
+    private $domain;
+
+    /**
+     * AccessKeyId
+     * @var string
+     */
+    private $accessKey;
+
+    /**
+     * AccessKeySecret
+     * @var string
+     */
+    private $secretKey;
+
+    /**
+     * @return $this
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    protected function initialize(): Storage
+    {
+        // 读取配置文件
+        $this->point = sysconf('storage.alioss_point');
+        $this->bucket = sysconf('storage.alioss_bucket');
+        $this->domain = sysconf('storage.alioss_http_domain');
+        $this->accessKey = sysconf('storage.alioss_access_key');
+        $this->secretKey = sysconf('storage.alioss_secret_key');
+        // 计算链接前缀
+        $type = strtolower(sysconf('storage.alioss_http_protocol'));
+        if ($type === 'auto') $this->prefix = "//{$this->domain}/";
+        elseif ($type === 'http') $this->prefix = "http://{$this->domain}/";
+        elseif ($type === 'https') $this->prefix = "https://{$this->domain}/";
+        else throw new \think\Exception('未配置阿里云URL域名哦');
+        return $this;
+    }
+
+    /**
+     * 获取当前实例对象
+     * @param null $name
+     * @return static
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function instance($name = null): Storage
+    {
+        return parent::instance('alioss');
+    }
+
+    /**
+     * 上传文件内容
+     * @param string $name 文件名称
+     * @param string $file 文件内容
+     * @param boolean $safe 安全模式
+     * @return array
+     */
+    public function set($name, $file, $safe = false)
+    {
+        $token = $this->buildUploadToken($name);
+        list($attrs, $frontier) = [[], uniqid()];
+        foreach (['key' => $name, 'policy' => $token['policy'], 'success_action_status' => '200', 'OSSAccessKeyId' => $this->accessKey, 'Signature' => $token['signature']] as $key => $value) {
+            $attrs[] = "--{$frontier}";
+            $attrs[] = "Content-Disposition: form-data; name=\"{$key}\"";
+            $attrs[] = "";
+            $attrs[] = $value;
+        }
+        $attrs[] = "--{$frontier}";
+        $attrs[] = "Content-Disposition: form-data; name=\"file\"; filename=\"{$name}\"";
+        $attrs[] = "";
+        $attrs[] = $file;
+        $attrs[] = "--{$frontier}--";
+        $result = HttpExtend::request('POST', $this->upload(), [
+            'data' => join("\r\n", $attrs), 'returnHeader' => true, 'headers' => ["Content-type:multipart/form-data;boundary={$frontier}"],
+        ]);
+        if (is_numeric(stripos($result, '200 OK'))) {
+            return ['file' => $this->path($name, $safe), 'url' => $this->url($name, $safe), 'key' => $name];
+        } else {
+            return [];
+        }
+    }
+
+    /**
+     * 根据文件名读取文件内容
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return false|string
+     */
+    public function get($name, $safe = false)
+    {
+        return file_get_contents($this->url($name, $safe) . "?e=" . time());
+    }
+
+    /**
+     * 删除存储的文件
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return boolean
+     */
+    public function del($name, $safe = false)
+    {
+        $result = HttpExtend::request('DELETE', "http://{$this->bucket}.{$this->point}/{$name}", [
+            'returnHeader' => true, 'headers' => $this->_signHeader('DELETE', $name),
+        ]);
+        return is_numeric(stripos($result, '204 No Content'));
+    }
+
+    /**
+     * 判断文件是否存在
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return boolean
+     */
+    public function has($name, $safe = false)
+    {
+        $result = HttpExtend::request('HEAD', "http://{$this->bucket}.{$this->point}/{$name}", [
+            'returnHeader' => true, 'headers' => $this->_signHeader('HEAD', $name),
+        ]);
+        return is_numeric(stripos($result, 'HTTP/1.1 200 OK'));
+    }
+
+    /**
+     * 获取文件当前URL地址
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return string
+     */
+    public function url($name, $safe = false)
+    {
+        return $this->prefix . $name;
+    }
+
+    /**
+     * 获取文件存储路径
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return string
+     */
+    public function path($name, $safe = false)
+    {
+        return $this->url($name, $safe);
+    }
+
+    /**
+     * 获取文件存储信息
+     * @param string $name 文件名称
+     * @param boolean $safe 安全模式
+     * @return array
+     */
+    public function info($name, $safe = false)
+    {
+        if ($this->has($name, $safe)) {
+            return ['file' => $this->path($name, $safe), 'url' => $this->url($name, $safe), 'key' => $name];
+        } else {
+            return [];
+        }
+    }
+
+    /**
+     * 获取文件上传地址
+     * @return string
+     */
+    public function upload()
+    {
+        $protocol = $this->app->request->isSsl() ? 'https' : 'http';
+        return "{$protocol}://{$this->bucket}.{$this->point}";
+    }
+
+    /**
+     * 获取文件上传令牌
+     * @param string $name 文件名称
+     * @param integer $expires 有效时间
+     * @return array
+     */
+    public function buildUploadToken($name = null, $expires = 3600)
+    {
+        $data = [
+            'policy'  => base64_encode(json_encode([
+                'conditions' => [['content-length-range', 0, 1048576000]],
+                'expiration' => date('Y-m-d\TH:i:s.000\Z', time() + $expires),
+            ])),
+            'siteurl' => $this->url($name),
+            'keyid'   => $this->accessKey,
+        ];
+        $data['signature'] = base64_encode(hash_hmac('sha1', $data['policy'], $this->secretKey, true));
+        return $data;
+    }
+
+    /**
+     * 操作请求头信息签名
+     * @param string $method 请求方式
+     * @param string $soruce 资源名称
+     * @param array $header 请求头信息
+     * @return array
+     */
+    private function _signHeader($method, $soruce, $header = [])
+    {
+        if (empty($header['Date'])) $header['Date'] = gmdate('D, d M Y H:i:s \G\M\T');
+        if (empty($header['Content-Type'])) $header['Content-Type'] = 'application/xml';
+        uksort($header, 'strnatcasecmp');
+        $content = "{$method}\n\n";
+        foreach ($header as $key => $value) {
+            $value = str_replace(["\r", "\n"], '', $value);
+            if (in_array(strtolower($key), ['content-md5', 'content-type', 'date'])) {
+                $content .= "{$value}\n";
+            } elseif (stripos($key, 'x-oss-') === 0) {
+                $content .= strtolower($key) . ":{$value}\n";
+            }
+        }
+        $content = rawurldecode($content) . "/{$this->bucket}/{$soruce}";
+        $signature = base64_encode(hash_hmac('sha1', $content, $this->secretKey, true));
+        $header['Authorization'] = "OSS {$this->accessKey}:{$signature}";
+        foreach ($header as $key => $value) $header[$key] = "{$key}: {$value}";
+        return array_values($header);
+    }
+
+}

+ 9 - 10
vendor/zoujingli/think-library/src/storage/LocalStorage.php

@@ -53,19 +53,19 @@ class LocalStorage extends Storage
      * @param string $name 文件名称
      * @param string $file 文件内容
      * @param boolean $safe 安全模式
-     * @return array|null
-     * @throws \think\Exception
+     * @return array
      */
     public function set($name, $file, $safe = false)
     {
         try {
             $path = $this->path($name, $safe);
             file_exists(dirname($path)) || mkdir(dirname($path), 0755, true);
-            if (file_put_contents($path, $file)) return $this->info($name, $safe);
+            if (file_put_contents($path, $file)) {
+                return $this->info($name, $safe);
+            }
         } catch (\Exception $e) {
-            throw new \think\Exception("本地文件存储失败,{$e->getMessage()}");
+            return [];
         }
-        return null;
     }
 
     /**
@@ -110,7 +110,7 @@ class LocalStorage extends Storage
      * 获取文件当前URL地址
      * @param string $name 文件名称
      * @param boolean $safe 安全模式
-     * @return boolean|string|null
+     * @return string|null
      */
     public function url($name, $safe = false)
     {
@@ -135,13 +135,12 @@ class LocalStorage extends Storage
      * 获取文件存储信息
      * @param string $name 文件名称
      * @param boolean $safe 安全模式
-     * @return array|null
+     * @return array
      */
     public function info($name, $safe = false)
     {
         return $this->has($name, $safe) ? [
-            'file' => $this->path($name, $safe), 'url' => $this->url($name, $safe),
-            'hash' => md5_file($this->path($name, $safe)), 'key' => "upload/{$name}",
+            'file' => $this->path($name, $safe), 'url' => $this->url($name, $safe), 'key' => "upload/{$name}",
         ] : [];
     }
 
@@ -151,7 +150,7 @@ class LocalStorage extends Storage
      */
     public function upload()
     {
-        return url('@')->build() . '?s=admin/api.upload/file';
+        return url('@admin/api.upload/file', [], false, true)->build();
     }
 
 }

+ 10 - 11
vendor/zoujingli/think-library/src/storage/QiniuStorage.php

@@ -32,7 +32,7 @@ class QiniuStorage extends Storage
 
     /**
      * 存储引擎初始化
-     * @return QiniuStorage
+     * @return $this
      * @throws \think\Exception
      * @throws \think\db\exception\DataNotFoundException
      * @throws \think\db\exception\DbException
@@ -42,9 +42,9 @@ class QiniuStorage extends Storage
     {
         // 读取配置文件
         $this->bucket = sysconf('storage.qiniu_bucket');
+        $this->domain = sysconf('storage.qiniu_http_domain');
         $this->accessKey = sysconf('storage.qiniu_access_key');
         $this->secretKey = sysconf('storage.qiniu_secret_key');
-        $this->domain = strtolower(sysconf('storage.qiniu_http_domain'));
         // 计算链接前缀
         $type = strtolower(sysconf('storage.qiniu_http_protocol'));
         if ($type === 'auto') $this->prefix = "//{$this->domain}/";
@@ -147,7 +147,7 @@ class QiniuStorage extends Storage
      */
     public function url($name, $safe = false)
     {
-        return "{$this->prefix}/{$name}";
+        return "{$this->prefix}{$name}";
     }
 
     /**
@@ -169,10 +169,8 @@ class QiniuStorage extends Storage
      */
     public function info($name, $safe = false)
     {
-        list($EncodedEntryURI, $AccessToken) = $this->getAccessToken($name);
-        $data = json_decode(HttpExtend::post("http://rs.qiniu.com/stat/{$EncodedEntryURI}", [], [
-            'headers' => ["Authorization:QBox {$AccessToken}"],
-        ]), true);
+        list($entry, $token) = $this->getAccessToken($name);
+        $data = json_decode(HttpExtend::get("http://rs.qiniu.com/stat/{$entry}", [], ['headers' => ["Authorization: QBox {$token}"]]), true);
         return isset($data['md5']) ? ['file' => $name, 'url' => $this->url($name, $safe), 'hash' => $data['md5'], 'key' => $name] : [];
     }
 
@@ -213,7 +211,7 @@ class QiniuStorage extends Storage
     {
         $policy = $this->safeBase64(json_encode([
             "deadline"   => time() + $expires, "scope" => is_null($name) ? $this->bucket : "{$this->bucket}:{$name}",
-            'returnBody' => json_encode(['uploaded' => true, 'filename' => '$(key)', 'url' => "{$this->prefix}/$(key)"], JSON_UNESCAPED_UNICODE),
+            'returnBody' => json_encode(['uploaded' => true, 'filename' => '$(key)', 'url' => "{$this->prefix}$(key)"], JSON_UNESCAPED_UNICODE),
         ]));
         return "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', $policy, $this->secretKey, true))}:{$policy}";
     }
@@ -234,9 +232,10 @@ class QiniuStorage extends Storage
      * @param string $type 操作类型
      * @return array
      */
-    private function getAccessToken($name, $type = 'state')
+    private function getAccessToken($name, $type = 'stat')
     {
-        $EncodedEntryURI = $this->safeBase64("{$this->bucket}:{$name}");
-        return [$EncodedEntryURI, "{$this->accessKey}:{$this->safeBase64(hash_hmac('sha1', "/{$type}/{$EncodedEntryURI}\n", $this->secretKey, true))}"];
+        $entry = $this->safeBase64("{$this->bucket}:{$name}");
+        $sign = hash_hmac('sha1', "/{$type}/{$entry}\n", $this->secretKey, true);
+        return [$entry, "{$this->accessKey}:{$this->safeBase64($sign)}"];
     }
 }

Some files were not shown because too many files changed in this diff