From ed20e5ac6ff43e0b1fee50d45e8274945979b857 Mon Sep 17 00:00:00 2001 From: Karson Date: Wed, 12 Aug 2020 21:49:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=8F=9C=E5=8D=95=E7=B1=BBup?= =?UTF-8?q?grade=E6=96=B9=E6=B3=95=20=E6=96=B0=E5=A2=9E=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E6=96=87=E4=BB=B6=E5=90=8D=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=E6=96=87=E4=BB=B6=E5=88=86=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=BB=84=E4=BB=B6=E5=8F=8A=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20=E4=BC=98=E5=8C=96=E7=B3=BB=E7=BB=9F=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=AD=E5=86=97=E4=BD=99=E7=9A=84JS=E4=BB=A3=E7=A0=81=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E6=8B=89=E6=A1=86=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=20=E4=BC=98=E5=8C=96=E5=A4=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=88=97=E8=A1=A8=E6=97=B6=E8=A1=A8=E6=A0=BC=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=B8=B2=E6=9F=93=E9=80=9F=E5=BA=A6=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=9C=A8PHP=E9=AB=98=E7=89=88=E6=9C=AC=E4=B8=8BToken?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E9=94=99=E8=AF=AF=E7=9A=84BUG=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=9C=A8PHP=E9=AB=98=E7=89=88=E6=9C=AC=E4=B8=8B?= =?UTF-8?q?=E4=BC=9A=E5=91=98=E7=99=BB=E5=BD=95=E9=A1=B5=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84BUG=20=E4=BF=AE=E5=A4=8DToken::get=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- application/admin/command/Crud.php | 4 +- .../admin/command/Install/fastadmin.sql | 4 +- application/admin/controller/Ajax.php | 144 +- .../admin/controller/general/Attachment.php | 8 +- application/admin/lang/zh-cn.php | 16 + application/admin/lang/zh-cn/ajax.php | 7 +- .../admin/lang/zh-cn/general/attachment.php | 1 + application/admin/library/traits/Backend.php | 2 + application/admin/view/addon/config.html | 8 +- application/admin/view/addon/index.html | 4 +- application/admin/view/category/add.html | 4 +- application/admin/view/category/edit.html | 4 +- .../admin/view/general/attachment/add.html | 4 +- .../admin/view/general/attachment/edit.html | 6 + .../admin/view/general/attachment/select.html | 2 +- .../admin/view/general/config/index.html | 6 +- .../admin/view/general/profile/index.html | 4 +- application/admin/view/index/login.html | 4 +- application/admin/view/user/user/edit.html | 4 +- application/api/controller/Common.php | 143 +- application/api/lang/zh-cn.php | 178 +- application/api/lang/zh-cn/common.php | 7 +- application/common/controller/Backend.php | 11 +- application/common/controller/Frontend.php | 2 +- .../common/exception/UploadException.php | 17 + application/common/library/Menu.php | 53 + application/common/library/Upload.php | 323 ++ application/common/model/Attachment.php | 2 +- application/common/model/Config.php | 39 +- application/config.php | 2 +- application/extra/upload.php | 4 + application/index/lang/zh-cn.php | 18 + application/index/lang/zh-cn/ajax.php | 7 +- application/index/lang/zh-cn/user.php | 1 - application/index/view/user/login.html | 24 +- application/index/view/user/profile.html | 6 +- bower.json | 1 - composer.json | 6 +- public/api.html | 209 +- public/assets/css/backend.css | 41 +- public/assets/css/backend.min.css | 2 +- public/assets/css/dropzone.min.css | 1 + public/assets/css/fastadmin.css | 5 +- public/assets/css/frontend.css | 32 +- public/assets/css/frontend.min.css | 2 +- public/assets/js/backend/addon.js | 4 +- .../assets/js/backend/general/attachment.js | 16 +- public/assets/js/backend/general/config.js | 43 +- public/assets/js/backend/general/profile.js | 6 +- public/assets/js/dropzone.js | 3851 +++++++++++++++++ public/assets/js/dropzone.min.js | 1 + public/assets/js/frontend/user.js | 4 +- public/assets/js/require-backend.js | 11 +- public/assets/js/require-backend.min.js | 436 +- public/assets/js/require-form.js | 20 +- public/assets/js/require-frontend.js | 17 +- public/assets/js/require-frontend.min.js | 17 +- public/assets/js/require-table.js | 16 +- public/assets/js/require-upload.js | 354 +- public/assets/less/backend.less | 43 +- public/assets/less/fastadmin/dropdown.less | 3 +- public/assets/less/frontend.less | 21 +- 63 files changed, 5296 insertions(+), 949 deletions(-) create mode 100644 application/common/exception/UploadException.php create mode 100644 application/common/library/Upload.php create mode 100755 public/assets/css/dropzone.min.css create mode 100755 public/assets/js/dropzone.js create mode 100755 public/assets/js/dropzone.min.js diff --git a/README.md b/README.md index 86f4b6f8..d6285cc3 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,17 @@ FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。 * 基于`Bootstrap`开发,自适应手机、平板、PC * 基于`RequireJS`进行JS模块管理,按需加载 * 基于`Less`进行样式开发 - * 基于`Bower`进行前端组件包管理 * 强大的插件扩展功能,在线安装卸载升级插件 * 通用的会员模块和API模块 * 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证 * 二级域名部署支持,同时域名支持绑定到插件 * 多语言支持,服务端及客户端支持 -* 强大的第三方模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)) +* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩 +* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[商城系统](https://www.fastadmin.net/store/shopro.html)) * 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html) * 第三方小程序支持([预订小程序](https://www.fastadmin.net/store/ball.html)、[问答小程序](https://www.fastadmin.net/store/questions.html)、[活动报名小程序](https://www.fastadmin.net/store/huodong.html)、[商城小程序](https://www.fastadmin.net/store/xshop.html)、[博客小程序](https://www.fastadmin.net/store/blog.html)) * 整合第三方短信接口(阿里云、腾讯云短信) -* 无缝整合第三方云存储(七牛、阿里云OSS、又拍云)功能 +* 无缝整合第三方云存储(七牛云、阿里云OSS、又拍云)功能 * 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器) * 第三方登录(QQ、微信、微博)整合 * 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付 @@ -80,6 +80,10 @@ Nice-validator: https://validator.niceue.com SelectPage: https://github.com/TerryZ/SelectPage +Layer: https://layer.layui.com + +DropzoneJS: https://www.dropzonejs.com + ## 版权信息 diff --git a/application/admin/command/Crud.php b/application/admin/command/Crud.php index 589a8bef..67036e2a 100755 --- a/application/admin/command/Crud.php +++ b/application/admin/command/Crud.php @@ -1394,12 +1394,12 @@ EOD; } $multiple = substr($field, -1) == 's' ? ' data-multiple="true"' : ' data-multiple="false"'; $preview = ' data-preview-id="p-' . $field . '"'; - $previewcontainer = $preview ? '' : ''; + $previewcontainer = $preview ? '' : ''; return << {$content}
- +
diff --git a/application/admin/command/Install/fastadmin.sql b/application/admin/command/Install/fastadmin.sql index 965b9fdf..8c6ea571 100755 --- a/application/admin/command/Install/fastadmin.sql +++ b/application/admin/command/Install/fastadmin.sql @@ -441,8 +441,8 @@ CREATE TABLE `fa_user` ( `gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别', `birthday` date DEFAULT NULL COMMENT '生日', `bio` varchar(100) NOT NULL DEFAULT '' COMMENT '格言', - `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '余额', - `score` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '积分', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额', + `score` int(10) NOT NULL DEFAULT '0' COMMENT '积分', `successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数', `maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数', `prevtime` int(10) DEFAULT NULL COMMENT '上次登录时间', diff --git a/application/admin/controller/Ajax.php b/application/admin/controller/Ajax.php index 92c157a7..816c2e83 100644 --- a/application/admin/controller/Ajax.php +++ b/application/admin/controller/Ajax.php @@ -3,6 +3,8 @@ namespace app\admin\controller; use app\common\controller\Backend; +use app\common\exception\UploadException; +use app\common\library\Upload; use fast\Random; use think\addons\Service; use think\Cache; @@ -54,99 +56,61 @@ class Ajax extends Backend public function upload() { Config::set('default_return_type', 'json'); - $file = $this->request->file('file'); - if (empty($file)) { - $this->error(__('No file upload or server upload limit exceeded')); - } - - //判断是否已经存在附件 - $sha1 = $file->hash(); - $extparam = $this->request->post(); - - $upload = Config::get('upload'); - - preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches); - $type = strtolower($matches[2]); - $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; - $size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0); - $fileInfo = $file->getInfo(); - $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); - $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; - - $mimetypeArr = explode(',', strtolower($upload['mimetype'])); - $typeArr = explode('/', $fileInfo['type']); - - //禁止上传PHP和HTML文件 - if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) { - $this->error(__('Uploaded file format is limited')); - } - //验证文件后缀 - if ($upload['mimetype'] !== '*' && - ( - !in_array($suffix, $mimetypeArr) - || (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr))) - ) - ) { - $this->error(__('Uploaded file format is limited')); - } - //验证是否为图片文件 - $imagewidth = $imageheight = 0; - if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { - $imgInfo = getimagesize($fileInfo['tmp_name']); - if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { - $this->error(__('Uploaded file is not a valid image')); + $chunkid = $this->request->post("chunkid"); + if ($chunkid) { + if (!Config::get('upload.chunking')) { + $this->error(__('Chunk file disabled')); + } + $action = $this->request->post("action"); + $chunkindex = $this->request->post("chunkindex/d"); + $chunkcount = $this->request->post("chunkcount/d"); + $filename = $this->request->post("filename"); + $method = $this->request->method(true); + if ($action == 'merge') { + $attachment = null; + //合并分片文件 + try { + $upload = new Upload(); + $attachment = $upload->merge($chunkid, $chunkcount, $filename); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(__('Uploaded successful'), '', ['url' => $attachment->url]); + } elseif ($method == 'clean') { + //删除冗余的分片文件 + try { + $upload = new Upload(); + $upload->clean($chunkid); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(); + } else { + //上传分片文件 + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $upload->chunk($chunkid, $chunkindex, $chunkcount); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(); } - $imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth; - $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight; - } - $replaceArr = [ - '{year}' => date("Y"), - '{mon}' => date("m"), - '{day}' => date("d"), - '{hour}' => date("H"), - '{min}' => date("i"), - '{sec}' => date("s"), - '{random}' => Random::alnum(16), - '{random32}' => Random::alnum(32), - '{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'], - '{suffix}' => $suffix, - '{.suffix}' => $suffix ? '.' . $suffix : '', - '{filemd5}' => md5_file($fileInfo['tmp_name']), - ]; - $savekey = $upload['savekey']; - $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey); - - $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1); - $fileName = substr($savekey, strripos($savekey, '/') + 1); - // - $splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName); - if ($splInfo) { - $params = array( - 'admin_id' => (int)$this->auth->id, - 'user_id' => 0, - 'filesize' => $fileInfo['size'], - 'imagewidth' => $imagewidth, - 'imageheight' => $imageheight, - 'imagetype' => $suffix, - 'imageframes' => 0, - 'mimetype' => $fileInfo['type'], - 'url' => $uploadDir . $splInfo->getSaveName(), - 'uploadtime' => time(), - 'storage' => 'local', - 'sha1' => $sha1, - 'extparam' => json_encode($extparam), - ); - $attachment = model("attachment"); - $attachment->data(array_filter($params)); - $attachment->save(); - \think\Hook::listen("upload_after", $attachment); - $this->success(__('Upload successful'), null, [ - 'url' => $uploadDir . $splInfo->getSaveName() - ]); } else { - // 上传失败获取错误信息 - $this->error($file->getError()); + $attachment = null; + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $attachment = $upload->upload(); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + + $this->success(__('Uploaded successful'), '', ['url' => $attachment->url]); } + } /** diff --git a/application/admin/controller/general/Attachment.php b/application/admin/controller/general/Attachment.php index b2a35d70..ba8cbb1c 100644 --- a/application/admin/controller/general/Attachment.php +++ b/application/admin/controller/general/Attachment.php @@ -105,9 +105,11 @@ class Attachment extends Backend { if ($ids) { \think\Hook::add('upload_delete', function ($params) { - $attachmentFile = ROOT_PATH . '/public' . $params['url']; - if (is_file($attachmentFile)) { - @unlink($attachmentFile); + if ($params['storage'] == 'local') { + $attachmentFile = ROOT_PATH . '/public' . $params['url']; + if (is_file($attachmentFile)) { + @unlink($attachmentFile); + } } }); $attachmentlist = $this->model->where('id', 'in', $ids)->select(); diff --git a/application/admin/lang/zh-cn.php b/application/admin/lang/zh-cn.php index 7c28b2bf..25de18da 100755 --- a/application/admin/lang/zh-cn.php +++ b/application/admin/lang/zh-cn.php @@ -143,8 +143,24 @@ return [ 'Please enter your username' => '请输入你的用户名', 'Please enter your password' => '请输入你的密码', 'Please login first' => '请登录后操作', + 'Uploaded successful' => '上传成功', 'You can upload up to %d file%s' => '你最多还可以上传%d个文件', 'You can choose up to %d file%s' => '你最多还可以选择%d个文件', + 'Chunk file write error' => '分片写入失败', + 'Chunk file info error' => '分片文件错误', + 'Chunk file merge error' => '分片合并错误', + 'Chunk file disabled' => '未开启分片上传功能', + 'Cancel upload' => '取消上传', + 'Upload canceled' => '上传已取消', + 'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制', + 'Uploaded file format is limited' => '上传文件格式受限制', + 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', + 'Are you sure you want to cancel this upload?' => '确定取消上传?', + 'Remove file' => '移除文件', + 'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件', + 'You can\'t upload files of this type' => '不允许上传的文件类型', + 'Server responded with %s code' => '服务端响应(Code:%s)', + 'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM', 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', //菜单 diff --git a/application/admin/lang/zh-cn/ajax.php b/application/admin/lang/zh-cn/ajax.php index ba01561a..0b67a5fe 100644 --- a/application/admin/lang/zh-cn/ajax.php +++ b/application/admin/lang/zh-cn/ajax.php @@ -1,8 +1,3 @@ '未上传文件或超出服务器上传限制', - 'Uploaded file format is limited' => '上传文件格式受限制', - 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', - 'Upload successful' => '上传成功', -]; +return []; diff --git a/application/admin/lang/zh-cn/general/attachment.php b/application/admin/lang/zh-cn/general/attachment.php index 96ea7af7..90f29aed 100644 --- a/application/admin/lang/zh-cn/general/attachment.php +++ b/application/admin/lang/zh-cn/general/attachment.php @@ -10,6 +10,7 @@ return [ 'Imagetype' => '图片类型', 'Imageframes' => '图片帧数', 'Preview' => '预览', + 'Filename' => '文件名', 'Filesize' => '文件大小', 'Mimetype' => 'Mime类型', 'Extparam' => '透传数据', diff --git a/application/admin/library/traits/Backend.php b/application/admin/library/traits/Backend.php index d365dea6..f3eab956 100755 --- a/application/admin/library/traits/Backend.php +++ b/application/admin/library/traits/Backend.php @@ -200,6 +200,7 @@ trait Backend */ public function del($ids = "") { + $ids = $this->request->post("ids"); if ($ids) { $pk = $this->model->getPk(); $adminIds = $this->getDataLimitAdminIds(); @@ -307,6 +308,7 @@ trait Backend public function multi($ids = "") { $ids = $ids ? $ids : $this->request->param("ids"); + $ids = $this->request->param("ids"); if ($ids) { if ($this->request->has('params')) { parse_str($this->request->post("params"), $values); diff --git a/application/admin/view/addon/config.html b/application/admin/view/addon/config.html index 2f1812d0..ebec7b2a 100644 --- a/application/admin/view/addon/config.html +++ b/application/admin/view/addon/config.html @@ -70,16 +70,16 @@ {case value="images"}
- + -
    +
      {/case} {case value="file" break="0"}{/case} {case value="files"}
      - +
      {/case} @@ -105,4 +105,4 @@ - \ No newline at end of file + diff --git a/application/admin/view/addon/index.html b/application/admin/view/addon/index.html index 26400cdf..83ec73f5 100644 --- a/application/admin/view/addon/index.html +++ b/application/admin/view/addon/index.html @@ -77,7 +77,7 @@
      {:build_toolbar('refresh')} - {if $Think.config.fastadmin.api_url} @@ -293,4 +293,4 @@ <% } %>
      - \ No newline at end of file + diff --git a/application/admin/view/category/add.html b/application/admin/view/category/add.html index a94bf7ef..5ca1ae07 100644 --- a/application/admin/view/category/add.html +++ b/application/admin/view/category/add.html @@ -58,12 +58,12 @@
      - +
      -
        +
          diff --git a/application/admin/view/category/edit.html b/application/admin/view/category/edit.html index ea8ecc48..7a554f36 100644 --- a/application/admin/view/category/edit.html +++ b/application/admin/view/category/edit.html @@ -54,12 +54,12 @@
          - +
          -
            +
              diff --git a/application/admin/view/general/attachment/add.html b/application/admin/view/general/attachment/add.html index a927d382..676cb581 100644 --- a/application/admin/view/general/attachment/add.html +++ b/application/admin/view/general/attachment/add.html @@ -10,7 +10,7 @@
              - +
              {/if} @@ -25,7 +25,7 @@
              - +
              diff --git a/application/admin/view/general/attachment/edit.html b/application/admin/view/general/attachment/edit.html index 7be19de3..8caa393e 100644 --- a/application/admin/view/general/attachment/edit.html +++ b/application/admin/view/general/attachment/edit.html @@ -30,6 +30,12 @@
              +
              + +
              + +
              +
              diff --git a/application/admin/view/general/attachment/select.html b/application/admin/view/general/attachment/select.html index f6c1ae1b..edd30fe5 100644 --- a/application/admin/view/general/attachment/select.html +++ b/application/admin/view/general/attachment/select.html @@ -17,7 +17,7 @@
              {:build_toolbar('refresh')} - + {if request()->get('multiple') == 'true'} {:__('Choose')} {/if} diff --git a/application/admin/view/general/config/index.html b/application/admin/view/general/config/index.html index db989c84..efa9cd66 100644 --- a/application/admin/view/general/config/index.html +++ b/application/admin/view/general/config/index.html @@ -111,17 +111,17 @@ {case value="images"}
              - + -
                +
                  {/case} {case value="file" break="0"}{/case} {case value="files"}
                  - +
                  diff --git a/application/admin/view/general/profile/index.html b/application/admin/view/general/profile/index.html index faa92677..d68f2ac0 100644 --- a/application/admin/view/general/profile/index.html +++ b/application/admin/view/general/profile/index.html @@ -51,9 +51,9 @@
                  - +
                  {:__('Click to edit')}
                  - +

                  {$admin.username|htmlentities}

                  diff --git a/application/admin/view/index/login.html b/application/admin/view/index/login.html index 9fdb3bff..e78450c6 100644 --- a/application/admin/view/index/login.html +++ b/application/admin/view/index/login.html @@ -85,7 +85,7 @@
                  {/if} -
                  +
                  {include file="common/script" /} - \ No newline at end of file + diff --git a/application/admin/view/user/user/edit.html b/application/admin/view/user/user/edit.html index d52c7cd7..77da6045 100644 --- a/application/admin/view/user/user/edit.html +++ b/application/admin/view/user/user/edit.html @@ -43,12 +43,12 @@
                  - +
                  -
                    +
                      diff --git a/application/api/controller/Common.php b/application/api/controller/Common.php index c5a7d101..aedcc610 100644 --- a/application/api/controller/Common.php +++ b/application/api/controller/Common.php @@ -3,6 +3,8 @@ namespace app\api\controller; use app\common\controller\Api; +use app\common\exception\UploadException; +use app\common\library\Upload; use app\common\model\Area; use app\common\model\Version; use fast\Random; @@ -47,96 +49,61 @@ class Common extends Api */ public function upload() { - $file = $this->request->file('file'); - if (empty($file)) { - $this->error(__('No file upload or server upload limit exceeded')); - } - - //判断是否已经存在附件 - $sha1 = $file->hash(); - - $upload = Config::get('upload'); - - preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches); - $type = strtolower($matches[2]); - $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; - $size = (int)$upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0); - $fileInfo = $file->getInfo(); - $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); - $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; - - $mimetypeArr = explode(',', strtolower($upload['mimetype'])); - $typeArr = explode('/', $fileInfo['type']); - - //禁止上传PHP和HTML文件 - if (in_array($fileInfo['type'], ['text/x-php', 'text/html']) || in_array($suffix, ['php', 'html', 'htm'])) { - $this->error(__('Uploaded file format is limited')); - } - //验证文件后缀 - if ($upload['mimetype'] !== '*' && - ( - !in_array($suffix, $mimetypeArr) - || (stripos($typeArr[0] . '/', $upload['mimetype']) !== false && (!in_array($fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr))) - ) - ) { - $this->error(__('Uploaded file format is limited')); - } - //验证是否为图片文件 - $imagewidth = $imageheight = 0; - if (in_array($fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { - $imgInfo = getimagesize($fileInfo['tmp_name']); - if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { - $this->error(__('Uploaded file is not a valid image')); + Config::set('default_return_type', 'json'); + $chunkid = $this->request->post("chunkid"); + if ($chunkid) { + if (!Config::get('upload.chunking')) { + $this->error(__('Chunk file disabled')); + } + $action = $this->request->post("action"); + $chunkindex = $this->request->post("chunkindex/d"); + $chunkcount = $this->request->post("chunkcount/d"); + $filename = $this->request->post("filename"); + $method = $this->request->method(true); + if ($action == 'merge') { + $attachment = null; + //合并分片文件 + try { + $upload = new Upload(); + $attachment = $upload->merge($chunkid, $chunkcount, $filename); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(__('Uploaded successful'), '', ['url' => $attachment->url]); + } elseif ($method == 'clean') { + //删除冗余的分片文件 + try { + $upload = new Upload(); + $upload->clean($chunkid); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(); + } else { + //上传分片文件 + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $upload->chunk($chunkid, $chunkindex, $chunkcount); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + $this->success(); } - $imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth; - $imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight; - } - $replaceArr = [ - '{year}' => date("Y"), - '{mon}' => date("m"), - '{day}' => date("d"), - '{hour}' => date("H"), - '{min}' => date("i"), - '{sec}' => date("s"), - '{random}' => Random::alnum(16), - '{random32}' => Random::alnum(32), - '{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'], - '{suffix}' => $suffix, - '{.suffix}' => $suffix ? '.' . $suffix : '', - '{filemd5}' => md5_file($fileInfo['tmp_name']), - ]; - $savekey = $upload['savekey']; - $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey); - - $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1); - $fileName = substr($savekey, strripos($savekey, '/') + 1); - // - $splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName); - if ($splInfo) { - $params = array( - 'admin_id' => 0, - 'user_id' => (int)$this->auth->id, - 'filesize' => $fileInfo['size'], - 'imagewidth' => $imagewidth, - 'imageheight' => $imageheight, - 'imagetype' => $suffix, - 'imageframes' => 0, - 'mimetype' => $fileInfo['type'], - 'url' => $uploadDir . $splInfo->getSaveName(), - 'uploadtime' => time(), - 'storage' => 'local', - 'sha1' => $sha1, - ); - $attachment = model("attachment"); - $attachment->data(array_filter($params)); - $attachment->save(); - \think\Hook::listen("upload_after", $attachment); - $this->success(__('Upload successful'), [ - 'url' => $uploadDir . $splInfo->getSaveName() - ]); } else { - // 上传失败获取错误信息 - $this->error($file->getError()); + $attachment = null; + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $attachment = $upload->upload(); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + + $this->success(__('Uploaded successful'), '', ['url' => $attachment->url]); } + } } diff --git a/application/api/lang/zh-cn.php b/application/api/lang/zh-cn.php index 35dafd70..1c85749f 100644 --- a/application/api/lang/zh-cn.php +++ b/application/api/lang/zh-cn.php @@ -1,84 +1,102 @@ '保持会话', - 'Username' => '用户名', - 'User id' => '会员ID', - 'Nickname' => '昵称', - 'Password' => '密码', - 'Sign up' => '注 册', - 'Sign in' => '登 录', - 'Sign out' => '注 销', - 'Guest' => '游客', - 'Welcome' => '%s,你好!', - 'Add' => '添加', - 'Edit' => '编辑', - 'Delete' => '删除', - 'Move' => '移动', - 'Name' => '名称', - 'Status' => '状态', - 'Weigh' => '权重', - 'Operate' => '操作', - 'Warning' => '温馨提示', - 'Default' => '默认', - 'Article' => '文章', - 'Page' => '单页', - 'OK' => '确定', - 'Cancel' => '取消', - 'Loading' => '加载中', - 'More' => '更多', - 'Normal' => '正常', - 'Hidden' => '隐藏', - 'Submit' => '提交', - 'Reset' => '重置', - 'Execute' => '执行', - 'Close' => '关闭', - 'Search' => '搜索', - 'Refresh' => '刷新', - 'First' => '首页', - 'Previous' => '上一页', - 'Next' => '下一页', - 'Last' => '末页', - 'None' => '无', - 'Home' => '主页', - 'Online' => '在线', - 'Logout' => '注销', - 'Profile' => '个人资料', - 'Index' => '首页', - 'Hot' => '热门', - 'Recommend' => '推荐', - 'Dashboard' => '控制台', - 'Code' => '编号', - 'Message' => '内容', - 'Line' => '行号', - 'File' => '文件', - 'Menu' => '菜单', - 'Type' => '类型', - 'Title' => '标题', - 'Content' => '内容', - 'Append' => '追加', - 'Memo' => '备注', - 'Parent' => '父级', - 'Params' => '参数', - 'Permission' => '权限', - 'Advance search' => '高级搜索', - 'Check all' => '选中全部', - 'Expand all' => '展开全部', - 'Begin time' => '开始时间', - 'End time' => '结束时间', - 'Create time' => '创建时间', - 'Flag' => '标志', - 'Please login first' => '请登录后操作', - 'Redirect now' => '立即跳转', - 'Operation completed' => '操作成功!', - 'Operation failed' => '操作失败!', - 'Unknown data format' => '未知的数据格式!', - 'Network error' => '网络错误!', - 'Advanced search' => '高级搜索', - 'Invalid parameters' => '未知参数', - 'No results were found' => '记录未找到', - 'Parameter %s can not be empty' => '参数%s不能为空', - 'You have no permission' => '你没有权限访问', - 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', - 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', + 'Keep login' => '保持会话', + 'Username' => '用户名', + 'User id' => '会员ID', + 'Nickname' => '昵称', + 'Password' => '密码', + 'Sign up' => '注 册', + 'Sign in' => '登 录', + 'Sign out' => '注 销', + 'Guest' => '游客', + 'Welcome' => '%s,你好!', + 'Add' => '添加', + 'Edit' => '编辑', + 'Delete' => '删除', + 'Move' => '移动', + 'Name' => '名称', + 'Status' => '状态', + 'Weigh' => '权重', + 'Operate' => '操作', + 'Warning' => '温馨提示', + 'Default' => '默认', + 'Article' => '文章', + 'Page' => '单页', + 'OK' => '确定', + 'Cancel' => '取消', + 'Loading' => '加载中', + 'More' => '更多', + 'Normal' => '正常', + 'Hidden' => '隐藏', + 'Submit' => '提交', + 'Reset' => '重置', + 'Execute' => '执行', + 'Close' => '关闭', + 'Search' => '搜索', + 'Refresh' => '刷新', + 'First' => '首页', + 'Previous' => '上一页', + 'Next' => '下一页', + 'Last' => '末页', + 'None' => '无', + 'Home' => '主页', + 'Online' => '在线', + 'Logout' => '注销', + 'Profile' => '个人资料', + 'Index' => '首页', + 'Hot' => '热门', + 'Recommend' => '推荐', + 'Dashboard' => '控制台', + 'Code' => '编号', + 'Message' => '内容', + 'Line' => '行号', + 'File' => '文件', + 'Menu' => '菜单', + 'Type' => '类型', + 'Title' => '标题', + 'Content' => '内容', + 'Append' => '追加', + 'Memo' => '备注', + 'Parent' => '父级', + 'Params' => '参数', + 'Permission' => '权限', + 'Advance search' => '高级搜索', + 'Check all' => '选中全部', + 'Expand all' => '展开全部', + 'Begin time' => '开始时间', + 'End time' => '结束时间', + 'Create time' => '创建时间', + 'Flag' => '标志', + 'Please login first' => '请登录后操作', + 'Uploaded successful' => '上传成功', + 'You can upload up to %d file%s' => '你最多还可以上传%d个文件', + 'You can choose up to %d file%s' => '你最多还可以选择%d个文件', + 'Chunk file write error' => '分片写入失败', + 'Chunk file info error' => '分片文件错误', + 'Chunk file merge error' => '分片合并错误', + 'Chunk file disabled' => '未开启分片上传功能', + 'Cancel upload' => '取消上传', + 'Upload canceled' => '上传已取消', + 'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制', + 'Uploaded file format is limited' => '上传文件格式受限制', + 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', + 'Are you sure you want to cancel this upload?' => '确定取消上传?', + 'Remove file' => '移除文件', + 'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件', + 'You can\'t upload files of this type' => '不允许上传的文件类型', + 'Server responded with %s code' => '服务端响应(Code:%s)', + 'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM', + 'Redirect now' => '立即跳转', + 'Operation completed' => '操作成功!', + 'Operation failed' => '操作失败!', + 'Unknown data format' => '未知的数据格式!', + 'Network error' => '网络错误!', + 'Advanced search' => '高级搜索', + 'Invalid parameters' => '未知参数', + 'No results were found' => '记录未找到', + 'Parameter %s can not be empty' => '参数%s不能为空', + 'You have no permission' => '你没有权限访问', + 'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中', + 'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转', ]; diff --git a/application/api/lang/zh-cn/common.php b/application/api/lang/zh-cn/common.php index ba01561a..0b67a5fe 100644 --- a/application/api/lang/zh-cn/common.php +++ b/application/api/lang/zh-cn/common.php @@ -1,8 +1,3 @@ '未上传文件或超出服务器上传限制', - 'Uploaded file format is limited' => '上传文件格式受限制', - 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', - 'Upload successful' => '上传成功', -]; +return []; diff --git a/application/common/controller/Backend.php b/application/common/controller/Backend.php index 1945fd82..40d2a60c 100644 --- a/application/common/controller/Backend.php +++ b/application/common/controller/Backend.php @@ -228,7 +228,7 @@ class Backend extends Controller */ protected function loadlang($name) { - $name = Loader::parseName($name); + $name = Loader::parseName($name); Lang::load(APP_PATH . $this->request->module() . '/lang/' . $this->request->langset() . '/' . str_replace('.', '/', $name) . '.php'); } @@ -456,8 +456,11 @@ class Backend extends Controller $where = function ($query) use ($word, $andor, $field, $searchfield, $custom) { $logic = $andor == 'AND' ? '&' : '|'; $searchfield = is_array($searchfield) ? implode($logic, $searchfield) : $searchfield; - foreach ($word as $k => $v) { - $query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%"); + $word = array_filter($word); + if ($word) { + foreach ($word as $k => $v) { + $query->where(str_replace(',', $logic, $searchfield), "like", "%{$v}%"); + } } if ($custom && is_array($custom)) { foreach ($custom as $k => $v) { @@ -517,7 +520,7 @@ class Backend extends Controller $token = $this->request->post('__token__'); //验证Token - if (!Validate::is($token, "token", ['__token__' => $token])) { + if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) { $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); } diff --git a/application/common/controller/Frontend.php b/application/common/controller/Frontend.php index ec1b76b7..593e360c 100644 --- a/application/common/controller/Frontend.php +++ b/application/common/controller/Frontend.php @@ -145,7 +145,7 @@ class Frontend extends Controller $token = $this->request->post('__token__'); //验证Token - if (!Validate::is($token, "token", ['__token__' => $token])) { + if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) { $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); } diff --git a/application/common/exception/UploadException.php b/application/common/exception/UploadException.php new file mode 100644 index 00000000..0381aa50 --- /dev/null +++ b/application/common/exception/UploadException.php @@ -0,0 +1,17 @@ +message = $message; + $this->code = $code; + $this->data = $data; + } + +} diff --git a/application/common/library/Menu.php b/application/common/library/Menu.php index 8386dcef..c8b15137 100644 --- a/application/common/library/Menu.php +++ b/application/common/library/Menu.php @@ -89,6 +89,21 @@ class Menu return true; } + /** + * 升级菜单 + * @param string $name 插件名称 + * @param array $menu 新菜单 + * @return bool + */ + public static function upgrade($name, $menu) + { + $old = AuthRule::where('name', 'like', "{$name}%")->select(); + $old = collection($old)->toArray(); + $old = array_column($old, null, 'name'); + self::menuUpdate($menu, $old); + return true; + } + /** * 导出指定名称的菜单规则 * @param string $name @@ -109,6 +124,44 @@ class Menu return $menuList; } + /** + * 菜单升级 + * @param array $newMenu + * @param array $oldMenu + * @param int $parent + * @throws Exception + */ + private static function menuUpdate($newMenu, $oldMenu, $parent = 0) + { + if (!is_numeric($parent)) { + $parentRule = AuthRule::getByName($parent); + $pid = $parentRule ? $parentRule['id'] : 0; + } else { + $pid = $parent; + } + $allow = array_flip(['file', 'name', 'title', 'icon', 'condition', 'remark', 'ismenu', 'weigh']); + foreach ($newMenu as $k => $v) { + $hasChild = isset($v['sublist']) && $v['sublist'] ? true : false; + $data = array_intersect_key($v, $allow); + $data['ismenu'] = isset($data['ismenu']) ? $data['ismenu'] : ($hasChild ? 1 : 0); + $data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o'); + $data['pid'] = $pid; + $data['status'] = 'normal'; + try { + if (!isset($oldMenu[$data['name']])) { + $menu = AuthRule::create($data); + } else { + $menu = $oldMenu[$data['name']]; + } + if ($hasChild) { + self::menuUpdate($v['sublist'], $oldMenu, $menu['id']); + } + } catch (PDOException $e) { + throw new Exception($e->getMessage()); + } + } + } + /** * 根据名称获取规则IDS * @param string $name diff --git a/application/common/library/Upload.php b/application/common/library/Upload.php new file mode 100644 index 00000000..34db9763 --- /dev/null +++ b/application/common/library/Upload.php @@ -0,0 +1,323 @@ +config = Config::get('upload'); + $this->chunkDir = RUNTIME_PATH . 'chunks'; + if ($file) { + $this->setFile($file); + } + } + + public function setChunkDir($dir) + { + $this->chunkDir = $dir; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + if (empty($file)) { + throw new UploadException(__('No file upload or server upload limit exceeded')); + } + + $fileInfo = $file->getInfo(); + $suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION)); + $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; + $fileInfo['suffix'] = $suffix; + $fileInfo['imagewidth'] = 0; + $fileInfo['imageheight'] = 0; + + $this->file = $file; + $this->fileInfo = $fileInfo; + } + + protected function checkExecutable() + { + //禁止上传PHP和HTML文件 + if (in_array($this->fileInfo['type'], ['text/x-php', 'text/html']) || in_array($this->fileInfo['suffix'], ['php', 'html', 'htm'])) { + throw new UploadException(__('Uploaded file format is limited')); + } + return true; + } + + protected function checkMimetype() + { + $mimetypeArr = explode(',', strtolower($this->config['mimetype'])); + $typeArr = explode('/', $this->fileInfo['type']); + //验证文件后缀 + if ($this->config['mimetype'] !== '*' && + (!in_array($this->fileInfo['suffix'], $mimetypeArr) || (stripos($typeArr[0] . '/', $this->config['mimetype']) !== false && (!in_array($this->fileInfo['type'], $mimetypeArr) && !in_array($typeArr[0] . '/*', $mimetypeArr)))) + ) { + throw new UploadException(__('Uploaded file format is limited')); + return false; + } + return true; + } + + protected function checkImage($force = false) + { + //验证是否为图片文件 + if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) { + $imgInfo = getimagesize($this->fileInfo['tmp_name']); + if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) { + throw new UploadException(__('Uploaded file is not a valid image')); + } + $this->fileInfo['imagewidth'] = isset($imgInfo[0]) ? $imgInfo[0] : 0; + $this->fileInfo['imageheight'] = isset($imgInfo[1]) ? $imgInfo[1] : 0; + return true; + } else { + return !$force; + } + } + + protected function checkSize() + { + preg_match('/([0-9\.]+)(\w+)/', $this->config['maxsize'], $matches); + $size = $matches ? $matches[1] : $this->config['maxsize']; + $type = $matches ? strtolower($matches[2]) : 'b'; + $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; + $size = (int)($size * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0)); + if ($this->fileInfo['size'] > $size) { + throw new UploadException(__('File is too big (%sMiB). Max filesize: %sMiB.', + round($this->fileInfo['size'] / pow(1024, 2), 2), + round($size / pow(1024, 2), 2))); + } + } + + public function getSuffix() + { + return $this->fileInfo['suffix'] ?: 'file'; + } + + public function getSavekey($savekey = null, $filename = null, $md5 = null) + { + if ($filename) { + $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; + } else { + $suffix = $this->fileInfo['suffix']; + } + $filename = $filename ? $filename : ($suffix ? substr($this->fileInfo['name'], 0, strripos($this->fileInfo['name'], '.')) : $this->fileInfo['name']); + $md5 = $md5 ? $md5 : md5_file($this->fileInfo['tmp_name']); + $replaceArr = [ + '{year}' => date("Y"), + '{mon}' => date("m"), + '{day}' => date("d"), + '{hour}' => date("H"), + '{min}' => date("i"), + '{sec}' => date("s"), + '{random}' => Random::alnum(16), + '{random32}' => Random::alnum(32), + '{filename}' => $filename, + '{suffix}' => $suffix, + '{.suffix}' => $suffix ? '.' . $suffix : '', + '{filemd5}' => $md5, + ]; + $savekey = $savekey ? $savekey : $this->config['savekey']; + $savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey); + + return $savekey; + } + + /** + * 清理分片文件 + * @param $chunkid + */ + public function clean($chunkid) + { + $iterator = new \GlobIterator($this->chunkDir . DS . $chunkid . '-*', FilesystemIterator::KEY_AS_FILENAME); + $array = iterator_to_array($iterator); + var_dump($array); + } + + public function merge($chunkid, $chunkcount, $filename) + { + $filePath = $this->chunkDir . DS . $chunkid; + + $completed = true; + //检查所有分片是否都存在 + for ($i = 0; $i < $chunkcount; $i++) { + if (!file_exists("{$filePath}-{$i}.part")) { + $completed = false; + break; + } + } + if (!$completed) { + throw new UploadException(__('Chunk file info error')); + } + + //如果所有文件分片都上传完毕,开始合并 + $uploadPath = $filePath; + + if (!$destFile = @fopen($uploadPath, "wb")) { + throw new UploadException(__('Chunk file merge error')); + } + if (flock($destFile, LOCK_EX)) { // 进行排他型锁定 + for ($i = 0; $i < $chunkcount; $i++) { + $partFile = "{$filePath}-{$i}.part"; + if (!$handle = @fopen($partFile, "rb")) { + break; + } + while ($buff = fread($handle, filesize($partFile))) { + fwrite($destFile, $buff); + } + @fclose($handle); + @unlink($partFile); //删除分片 + } + + flock($destFile, LOCK_UN); + } + @fclose($destFile); + + $file = new File($uploadPath); + $info = [ + 'name' => $filename, + 'type' => $file->getMime(), + 'tmp_name' => $uploadPath, + 'error' => 0, + 'size' => $file->getSize() + ]; + $file->setUploadInfo($info); + $file->isTest(true); + + //重新设置文件 + $this->setFile($file); + + //允许大文件 + $this->config['maxsize'] = "1024G"; + + return $this->upload(); + } + + /** + * 分片上传 + * @throws UploadException + */ + public function chunk($chunkid, $chunkindex, $chunkcount, $chunkfilesize = null, $chunkfilename = null, $direct = false) + { + + if ($this->fileInfo['type'] != 'application/octet-stream') { + throw new UploadException(__('Uploaded file format is limited')); + } + + $destDir = RUNTIME_PATH . 'chunks'; + $fileName = $chunkid . "-" . $chunkindex . '.part'; + $destFile = $destDir . DS . $fileName; + if (!move_uploaded_file($this->file->getPathname(), $destFile)) { + throw new UploadException(__('Chunk file write error')); + } + $file = new File($destFile); + $this->setFile($file); + return $file; + } + + /** + * 普通上传 + * @return \app\common\model\attachment|\think\Model + * @throws UploadException + */ + public function upload($savekey = null) + { + if (empty($this->file)) { + throw new UploadException(__('No file upload or server upload limit exceeded')); + } + + $this->checkSize(); + $this->checkExecutable(); + $this->checkMimetype(); + $this->checkImage(); + + $savekey = $savekey ? $savekey : $this->getSavekey(); + $savekey = '/' . ltrim($savekey, '/'); + $uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1); + $fileName = substr($savekey, strripos($savekey, '/') + 1); + + $destDir = ROOT_PATH . 'public' . $uploadDir; + + $sha1 = $this->file->hash(); + + $file = $this->file->move($destDir, $fileName); + if (!$file) { + // 上传失败获取错误信息 + throw new UploadException($this->file->getError()); + } + $this->file = $file; + $params = array( + 'admin_id' => (int)session('admin.id'), + 'user_id' => (int)cookie('uid'), + 'filename' => htmlspecialchars(strip_tags($this->fileInfo['name'])), + 'filesize' => $this->fileInfo['size'], + 'imagewidth' => $this->fileInfo['imagewidth'], + 'imageheight' => $this->fileInfo['imageheight'], + 'imagetype' => $this->fileInfo['suffix'], + 'imageframes' => 0, + 'mimetype' => $this->fileInfo['type'], + 'url' => $uploadDir . $file->getSaveName(), + 'uploadtime' => time(), + 'storage' => 'local', + 'sha1' => $sha1, + 'extparam' => '', + ); + $attachment = new Attachment(); + $attachment->data(array_filter($params)); + $attachment->save(); + + \think\Hook::listen("upload_after", $attachment); + return $attachment; + } + + public function setError($msg) + { + $this->error = $msg; + } + + public function getError() + { + return $this->error; + } +} diff --git a/application/common/model/Attachment.php b/application/common/model/Attachment.php index 81a25577..43ab2ec6 100644 --- a/application/common/model/Attachment.php +++ b/application/common/model/Attachment.php @@ -38,7 +38,7 @@ class Attachment extends Model { // 如果已经上传该资源,则不再记录 self::beforeInsert(function ($model) { - if (self::where('url', '=', $model['url'])->find()) { + if (self::where('url', '=', $model['url'])->where('storage', $model['storage'])->find()) { return false; } }); diff --git a/application/common/model/Config.php b/application/common/model/Config.php index cccc5d35..d8efa4e6 100644 --- a/application/common/model/Config.php +++ b/application/common/model/Config.php @@ -29,24 +29,27 @@ class Config extends Model public static function getTypeList() { $typeList = [ - 'string' => __('String'), - 'text' => __('Text'), - 'editor' => __('Editor'), - 'number' => __('Number'), - 'date' => __('Date'), - 'time' => __('Time'), - 'datetime' => __('Datetime'), - 'select' => __('Select'), - 'selects' => __('Selects'), - 'image' => __('Image'), - 'images' => __('Images'), - 'file' => __('File'), - 'files' => __('Files'), - 'switch' => __('Switch'), - 'checkbox' => __('Checkbox'), - 'radio' => __('Radio'), - 'array' => __('Array'), - 'custom' => __('Custom'), + 'string' => __('String'), + 'text' => __('Text'), + 'editor' => __('Editor'), + 'number' => __('Number'), + 'date' => __('Date'), + 'time' => __('Time'), + 'datetime' => __('Datetime'), + 'select' => __('Select'), + 'selects' => __('Selects'), + 'image' => __('Image'), + 'images' => __('Images'), + 'file' => __('File'), + 'files' => __('Files'), + 'switch' => __('Switch'), + 'checkbox' => __('Checkbox'), + 'radio' => __('Radio'), + 'city' => __('City'), + 'selectpage' => __('Selectpage'), + 'selectpages' => __('Selectpages'), + 'array' => __('Array'), + 'custom' => __('Custom'), ]; return $typeList; } diff --git a/application/config.php b/application/config.php index 155105ff..1299f899 100755 --- a/application/config.php +++ b/application/config.php @@ -276,7 +276,7 @@ return [ //自动检测更新 'checkupdate' => false, //版本号 - 'version' => '1.1.0.20200612_beta', + 'version' => '1.2.0', //API接口地址 'api_url' => 'https://api.fastadmin.net', ], diff --git a/application/extra/upload.php b/application/extra/upload.php index 5bfa25d0..c53b104e 100644 --- a/application/extra/upload.php +++ b/application/extra/upload.php @@ -26,4 +26,8 @@ return [ * 是否支持批量上传 */ 'multiple' => false, + /** + * 是否支持分片上传 + */ + 'chunking' => false, ]; diff --git a/application/index/lang/zh-cn.php b/application/index/lang/zh-cn.php index 40350045..3f8a0085 100755 --- a/application/index/lang/zh-cn.php +++ b/application/index/lang/zh-cn.php @@ -107,6 +107,24 @@ return [ 'User center' => '会员中心', 'Change password' => '修改密码', 'Please login first' => '请登录后再操作', + 'Uploaded successful' => '上传成功', + 'You can upload up to %d file%s' => '你最多还可以上传%d个文件', + 'You can choose up to %d file%s' => '你最多还可以选择%d个文件', + 'Chunk file write error' => '分片写入失败', + 'Chunk file info error' => '分片文件错误', + 'Chunk file merge error' => '分片合并错误', + 'Chunk file disabled' => '未开启分片上传功能', + 'Cancel upload' => '取消上传', + 'Upload canceled' => '上传已取消', + 'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制', + 'Uploaded file format is limited' => '上传文件格式受限制', + 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', + 'Are you sure you want to cancel this upload?' => '确定取消上传?', + 'Remove file' => '移除文件', + 'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件', + 'You can\'t upload files of this type' => '不允许上传的文件类型', + 'Server responded with %s code' => '服务端响应(Code:%s)', + 'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM', 'Send verification code' => '发送验证码', 'Redirect now' => '立即跳转', 'Operation completed' => '操作成功!', diff --git a/application/index/lang/zh-cn/ajax.php b/application/index/lang/zh-cn/ajax.php index ba01561a..0b67a5fe 100644 --- a/application/index/lang/zh-cn/ajax.php +++ b/application/index/lang/zh-cn/ajax.php @@ -1,8 +1,3 @@ '未上传文件或超出服务器上传限制', - 'Uploaded file format is limited' => '上传文件格式受限制', - 'Uploaded file is not a valid image' => '上传文件不是有效的图片文件', - 'Upload successful' => '上传成功', -]; +return []; diff --git a/application/index/lang/zh-cn/user.php b/application/index/lang/zh-cn/user.php index c9c7430d..6bd21edc 100755 --- a/application/index/lang/zh-cn/user.php +++ b/application/index/lang/zh-cn/user.php @@ -55,7 +55,6 @@ return [ 'New mobile' => '新手机号', 'Change password successful' => '修改密码成功', 'Captcha is incorrect' => '验证码不正确', - 'Upload successful' => '上传成功', 'Logged in successful' => '登录成功', 'Logout successful' => '注销成功', 'User center already closed' => '会员中心已经关闭', diff --git a/application/index/view/user/login.html b/application/index/view/user/login.html index ff2af389..5c159261 100755 --- a/application/index/view/user/login.html +++ b/application/index/view/user/login.html @@ -1,9 +1,9 @@
                      @@ -3137,7 +3138,7 @@
                      @@ -3155,7 +3156,7 @@
                      - 检测手机
                      + 检测昵称
                      Headers
                      @@ -3176,10 +3177,10 @@ - mobile + nickname string 是 - 手机号 + 昵称 id @@ -3204,10 +3205,10 @@
                      参数
                      - +
                      - - + +
                      @@ -3257,7 +3258,7 @@
                      @@ -3300,6 +3301,12 @@ string 是 手机号 + + + id + string + 是 + 排除会员ID @@ -3318,10 +3325,14 @@
                      参数
                      - +
                      +
                      +
                      + +
                      @@ -3367,7 +3378,7 @@
                      @@ -3385,7 +3396,7 @@
                      - 检测邮箱
                      + 检测手机
                      Headers
                      @@ -3409,7 +3420,7 @@ mobile string 是 - 邮箱 + 手机号 @@ -3428,10 +3439,10 @@
                      参数
                      - +
                      - +
                      @@ -3477,7 +3488,7 @@
                      @@ -3494,6 +3505,116 @@
                      +
                      + 检测邮箱
                      +
                      +
                      Headers
                      +
                      + 无 +
                      +
                      +
                      +
                      参数
                      +
                      + + + + + + + + + + + + + + + + + +
                      名称类型必选描述
                      mobilestring邮箱
                      +
                      +
                      +
                      +
                      正文
                      +
                      + 无
                      +
                      +
                      + +
                      +
                      +
                      +
                      +
                      参数
                      +
                      + +
                      + + +
                      +
                      + + +
                      + +
                      +
                      +
                      +
                      响应输出
                      +
                      +
                      +
                      +
                      
                      +                                                            
                      
                      +                                                        
                      +
                      +
                      +
                      +
                      +
                      返回参数
                      +
                      + 无 +
                      +
                      +
                      +
                      +
                      + +
                      +
                      +
                      +
                      +
                      +
                      +
                      + +
                      +
                      +
                      +
                      +
                      + +
                      +
                      + + + + + +
                      + +
                      检测手机验证码
                      @@ -3544,13 +3665,13 @@
                      -
                      +
                      参数
                      -
                      +
                      @@ -3564,8 +3685,8 @@
                      - - + +
                      @@ -3575,8 +3696,8 @@
                      -
                      
                      -                                                            
                      
                      +                                                            
                      
                      +                                                            
                      
                                                                               
                      @@ -3591,10 +3712,10 @@
                      -
                      +
                      -
                      +
                      @@ -3604,26 +3725,26 @@
                      -
                      + -
                      +
                      -