linwu 3 ani în urmă
comite
c93a1eba5c
100 a modificat fișierele cu 12075 adăugiri și 0 ștergeri
  1. 2 0
      .dockerignore
  2. 11 0
      .gitignore
  3. 2 0
      CONTRIBUTING.md
  4. 40 0
      Dockerfile
  5. 21 0
      LICENSE
  6. 242 0
      README.md
  7. 431 0
      api/portal/controller/ArticlesController.php
  8. 81 0
      api/portal/controller/CategoriesController.php
  9. 76 0
      api/portal/controller/IndexController.php
  10. 79 0
      api/portal/controller/ListsController.php
  11. 65 0
      api/portal/controller/PagesController.php
  12. 86 0
      api/portal/controller/TagsController.php
  13. 140 0
      api/portal/controller/UserArticlesController.php
  14. 41 0
      api/portal/controller/UserController.php
  15. 324 0
      api/portal/logic/PortalPostModel.php
  16. 82 0
      api/portal/model/PortalCategoryModel.php
  17. 24 0
      api/portal/model/PortalCategoryPostModel.php
  18. 491 0
      api/portal/model/PortalPostModel.php
  19. 25 0
      api/portal/model/PortalTagModel.php
  20. 29 0
      api/portal/model/PortalTagPostModel.php
  21. 16 0
      api/portal/model/RecycleBinModel.php
  22. 41 0
      api/portal/model/UserModel.php
  23. 22 0
      api/portal/route.php
  24. 59 0
      api/portal/service/PortalCategoryService.php
  25. 108 0
      api/portal/service/PortalPostService.php
  26. 100 0
      api/portal/service/PortalTagService.php
  27. 34 0
      api/portal/validate/ArticlesValidate.php
  28. 376 0
      app/admin/controller/UserController.php
  29. 33 0
      app/admin/validate/UserValidate.php
  30. 67 0
      app/portal/api/CategoryApi.php
  31. 76 0
      app/portal/api/PageApi.php
  32. 460 0
      app/portal/controller/AdminArticleController.php
  33. 370 0
      app/portal/controller/AdminCategoryController.php
  34. 32 0
      app/portal/controller/AdminIndexController.php
  35. 239 0
      app/portal/controller/AdminPageController.php
  36. 153 0
      app/portal/controller/AdminTagController.php
  37. 113 0
      app/portal/controller/ArticleController.php
  38. 36 0
      app/portal/controller/IndexController.php
  39. 70 0
      app/portal/controller/ListController.php
  40. 44 0
      app/portal/controller/PageController.php
  41. 32 0
      app/portal/controller/SearchController.php
  42. 47 0
      app/portal/controller/TagController.php
  43. 113 0
      app/portal/data/portal.sql
  44. 96 0
      app/portal/hooks.php
  45. 13 0
      app/portal/lang/en-us.php
  46. 15 0
      app/portal/lang/en-us/common.php
  47. 21 0
      app/portal/lang/zh-cn.php
  48. 16 0
      app/portal/lang/zh-cn/common.php
  49. 13 0
      app/portal/lang/zh-cn/home.php
  50. 217 0
      app/portal/model/PortalCategoryModel.php
  51. 416 0
      app/portal/model/PortalPostModel.php
  52. 21 0
      app/portal/model/PortalTagModel.php
  53. 23 0
      app/portal/model/UserModel.php
  54. 14 0
      app/portal/nav.php
  55. 459 0
      app/portal/service/ApiService.php
  56. 287 0
      app/portal/service/PostService.php
  57. 372 0
      app/portal/taglib/Portal.php
  58. 53 0
      app/portal/url.php
  59. 15 0
      app/portal/user_action.php
  60. 30 0
      app/portal/validate/AdminArticleValidate.php
  61. 51 0
      app/portal/validate/AdminPageValidate.php
  62. 55 0
      app/portal/validate/PortalCategoryValidate.php
  63. 210 0
      app/test/controller/IndexController.php
  64. 44 0
      composer.json
  65. 1883 0
      composer.lock
  66. 2 0
      data/.gitignore
  67. 29 0
      docker-compose.yml
  68. 12 0
      public/.htaccess
  69. 42 0
      public/api.php
  70. 33 0
      public/index.php
  71. 90 0
      public/plugins/qiniu/QiniuPlugin.php
  72. 15 0
      public/plugins/qiniu/composer.json
  73. 69 0
      public/plugins/qiniu/composer.lock
  74. 178 0
      public/plugins/qiniu/config.php
  75. 130 0
      public/plugins/qiniu/controller/AssetController.php
  76. 876 0
      public/plugins/qiniu/error.html
  77. 166 0
      public/plugins/qiniu/lib/Qiniu.php
  78. 7 0
      public/plugins/qiniu/vendor/autoload.php
  79. 445 0
      public/plugins/qiniu/vendor/composer/ClassLoader.php
  80. 21 0
      public/plugins/qiniu/vendor/composer/LICENSE
  81. 9 0
      public/plugins/qiniu/vendor/composer/autoload_classmap.php
  82. 10 0
      public/plugins/qiniu/vendor/composer/autoload_files.php
  83. 9 0
      public/plugins/qiniu/vendor/composer/autoload_namespaces.php
  84. 10 0
      public/plugins/qiniu/vendor/composer/autoload_psr4.php
  85. 70 0
      public/plugins/qiniu/vendor/composer/autoload_real.php
  86. 35 0
      public/plugins/qiniu/vendor/composer/autoload_static.php
  87. 55 0
      public/plugins/qiniu/vendor/composer/installed.json
  88. 12 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/.gitignore
  89. 35 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/.scrutinizer.yml
  90. 23 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/.travis.yml
  91. 105 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/CHANGELOG.md
  92. 30 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/CONTRIBUTING.md
  93. 22 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/LICENSE
  94. 75 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/README.md
  95. 14 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/autoload.php
  96. 26 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/composer.json
  97. 71 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/docs/rtc/README.md
  98. 42 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/docs/rtc/example.php
  99. 10 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/examples/README.md
  100. 40 0
      public/plugins/qiniu/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php

+ 2 - 0
.dockerignore

@@ -0,0 +1,2 @@
+# Created by .ignore support plugin (hsz.mobi)
+.git*

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+.buildpath
+.DS_Store
+.project
+.settings
+.idea
+.git
+/build
+/public/assets/dist
+/node_modules
+Vagrantfile
+.vagrant

+ 2 - 0
CONTRIBUTING.md

@@ -0,0 +1,2 @@
+如何贡献我的源代码
+===

+ 40 - 0
Dockerfile

@@ -0,0 +1,40 @@
+FROM php:7.1-apache
+
+MAINTAINER jayknoxqu@gmail.com
+
+#设置容器时区
+ENV TZ Asia/Shanghai
+
+#设置程序入口
+ENV APACHE_DOCUMENT_ROOT /var/www/html/public
+
+RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone \
+    && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
+    && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
+    #添加阿里云的镜像源
+    && mv /etc/apt/sources.list /etc/apt/sources.list.bak \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch main non-free contrib" >/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian-security stretch/updates main" >>/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >>/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >>/etc/apt/sources.list \
+    #安装程序依赖库
+    && apt-get update && apt-get install -y \
+              libfreetype6-dev \
+              libjpeg62-turbo-dev \
+              libpng-dev \
+    #安装 PHP 依赖
+    && docker-php-ext-install pdo_mysql \
+    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+    && docker-php-ext-install -j$(nproc) gd \
+    #删除包缓存中的所有包
+    && apt-get clean \
+    && apt-get autoclean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+#复制代码到PHP容器
+COPY . /var/www/html
+
+# 开启URL重写 并且 添加目录权限
+RUN a2enmod rewrite \
+    && chmod -R 0755 /var/www/html \
+    && chown -R www-data:www-data /var/www/html

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2013-present ThinkCMF (https://www.thinkcmf.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 242 - 0
README.md

@@ -0,0 +1,242 @@
+ThinkCMF 5.1.5 让你更自由地飞
+===============
+
+### 系列讲座
+https://www.thinkcmf.com/college.html
+
+### ThinkCMF5.1主要特性
+* 更改框架协议为`MIT`,让你更自由地飞
+* 基于`ThinkPHP 5.1`重构,但核心代码兼容5.0版本,保证老用户最小升级成本
+* 增加对`swoole`支持,同时支持`swoole`协程和全同步模式
+* 重新规范目录结构,更贴心
+* CMF核心库及应用使用`composer`加载
+* 合并API到框架核心
+* 更规范的代码,遵循`PSR-2`命名规范和`PSR-4`自动加载规范
+* 支持 `composer` 管理第三方库
+* 核心化:独立核心代码包
+* 应用化:开发者以应用的形式增加项目模模块
+* 插件化:更强的插件机制,开发者以插件形式扩展功能
+* 模板化:前台可视化设计
+* 支持URL美化功能,支持别名设置,更简单
+* 独立的回收站功能,可以管理所有应用临时删除的数据
+* 统一的资源管理,相同文件只保存一份
+* 注解式的后台菜单管理功能,方便开发者代码管理后台菜单
+* 插件同样支持注解式的后台菜单管理功能
+* 文件存储插件化,默认支持七牛文件存储插件
+* 模板制作标签化,内置多个cmf标签,方便小白用户
+* 更人性化的导航标签,可以随意定制 html 结构
+* 后台首页插件化,用户可以定制的网站后台首页
+
+### 开发手册
+http://www.kancloud.cn/thinkcmf/doc5_1
+
+### Git仓库
+
+1. GitHub:https://github.com/thinkcmf/thinkcmf 主要仓库
+2. 码云:https://gitee.com/thinkcmf/ThinkCMF 中国镜像
+
+### 演示仓库
+此仓库会放官方的一些演示应用,插件,模板,API等 
+1. https://github.com/thinkcmf/demos 主要仓库
+2. https://gitee.com/thinkcmf/demos 中国镜像
+
+### 环境推荐
+> php7.1
+
+> mysql 5.6+
+
+> 打开rewrite
+
+
+### 最低环境要求
+> php5.6+
+
+> mysql 5.5+ (mysql5.1安装时选择utf8编码,不支持表情符)
+
+> 打开rewrite
+
+
+### 运行环境配置教程
+https://www.thinkcmf.com/topic/1502.html
+
+
+
+代码已经加入自动安装程序,如果你在安装中有任何问题请提交 issue!
+
+1. public目录做为网站根目录,入口文件在 public/index.php
+2. 配置好网站,请访问http://你的域名
+
+enjoy your cmf~!
+
+### 系统更新
+如果您是已经安装过ThinkCMF的用户,请查看 update 目录下的 sql 升级文件,根据自己的下载的程序版本进行更新
+
+### 完整版目录结构
+~~~
+thinkcmf  根目录
+├─api                     api目录
+│  ├─demo                 演示应用api目录
+│  │  ├─controller        控制器目录
+│  │  ├─model             模型目录
+│  │  └─ ...              更多类库目录
+├─app                     应用目录
+│  ├─demo                 演示应用目录
+│  │  ├─controller        控制器目录
+│  │  ├─model             模型目录
+│  │  └─ ...              更多类库目录
+│  ├─ ...                 更多应用
+│  ├─app.php              应用(公共)配置文件[可选]
+│  ├─command.php          命令行工具配置文件[可选]
+│  ├─common.php           应用公共(函数)文件[可选]
+│  ├─database.php         数据库配置文件[可选]
+│  ├─tags.php             应用行为扩展定义文件[可选]
+├─data                    数据目录(可写)
+│  ├─config               动态配置目录(可写)
+│  ├─route                动态路由目录(可写)
+│  ├─runtime              应用的运行时目录(可写)
+│  └─ ...                 更多
+├─public                  WEB 部署目录(对外访问目录)
+│  ├─plugins              插件目录
+│  ├─static               官方静态资源存放目录(css,js,image),勿放自己项目文件
+│  ├─themes               前后台主题目录
+│  │  ├─admin_simpleboot3 后台默认主题
+│  │  └─default           前台默认主题
+│  ├─upload               文件上传目录
+│  ├─api.php              API入口
+│  ├─index.php            入口文件
+│  ├─robots.txt           爬虫协议文件
+│  ├─router.php           快速测试文件
+│  └─.htaccess            apache重写文件
+├─extend                  扩展类库目录[可选]
+├─vendor                  第三方类库目录(Composer)
+│  ├─thinkphp             ThinkPHP目录
+│  └─...             
+├─composer.json           composer 定义文件
+├─LICENSE                 授权说明文件
+├─README.md               README 文件
+├─think                   命令行入口文件
+~~~
+
+### QQ群:
+`ThinkCMF 官方交流群`:316669417  
+   
+`ThinkCMF 高级交流群`:100828313 (付费)  
+高级群专属权益:  
+第一波:两个后台风格(ThinkCMF官网风格后台主题,蓝色风格后台主题)  
+第二波:ThinkCMF5完全开发手册离线版(PDF,EPUB,MOBI格式)  
+更多专属权益正在路上...
+
+`ThinkCMF 铲屎官交流群`:415136742 (生活娱乐,为有喵的猿人准备)
+
+### 话题专区
+http://www.thinkcmf.com/topic/index/index/cat/11.html
+
+### 反馈问题
+https://github.com/thinkcmf/thinkcmf/issues
+
+### 更新日志
+#### 5.1.5
+* 升级到tp5.1.39
+* 增加模板设计数组列表图片显示
+* 优化前台基类
+* 取消路由排序限制
+
+#### 5.1.4
+* 优化上传逻辑,已传文件更新文件名
+* 优化系统钩子初始化
+* 修复编辑器锚点处理错误
+* 修复部分系统函数判断问题
+* 修复tp5.1.38前台控制器报错
+* 修复tp5.1.38下邮件验证码发不出
+
+#### 5.1.3
+* 增加`CMF_DATA`常量(注意升级)
+* 增加插件路由功能
+* 增加插件URL美化功能
+* 修复app_init钩子引起的命令行报错
+* 修复API中文件url转化错误
+
+#### 5.1.2
+[核心]
+* 升级tp到`5.1.37`
+* 优化`slides,noslides`标签
+* 修复头像地址获取函数
+* 优化上传类支持API文件上传
+* 封装后台菜单,应用钩子,用户行为导入
+* 增加应用自动安装
+* 优化后台百度地图链接支持https
+
+[API]
+* 优化文件上传,支持云存储
+* 修复积分日志接口数据返回错误
+* 修复钩子不加载问题
+* 修复API跨域报错问题
+
+
+#### 5.1.1
+[核心]
+* `composer.json` extra 增加`think-config`配置
+* 修复API UserLikeModel继承错误类
+* 优化后台菜单 url 生成
+* 增加Linux下全新安装时data目录不可写提示
+* 修复插件模板常量`__ROOT__`不替换
+* 增加`swoole`扩展钩子检测
+* 修复插件API基类报错#577
+* 优化应用初始化流程
+* 优化行为加载流程
+
+[swoole]
+* 增加`swoole_server_start`,`swoole_worker_start`,`swoole_websocket_on_open`,`swoole_websocket_on_close`钩子
+* 增加`WebSocket`独立运行命令
+* 增加`WebSocket onOpen`回调
+* 修复`WebSocket`事件引起的数据库执行报错
+* 修复`WebSocket`发送消息未判断是否为`WebSocket`连接
+* 增加`worker`进程启动时自动初始化所有模块
+
+
+
+#### 5.1.0
+[核心]
+* 更改框架协议为`MIT`,让你更自由地飞
+* 升级`TP`到`5.1.35`
+* 独立安装应用为`composer`包
+* 移除portal应用,请到`https://github.com/thinkcmf/demos`下载
+* 移除`simpleboot3`模板,请到`https://github.com/thinkcmf/demos`下载
+* 移除`phpoffice/phpspreadsheet`,`phpoffice/phpexcel`,`dompdf/dompdf`第三方库,请自行安装
+* 移动`qiniu/php-sdk`库到七牛插件
+* `extend`目录改为可选,开发者自行添加,核心不再包含此目录
+* 增加`demo`应用,方便开发者学习
+* 增加插件`@adminMenuRoot`注解支持
+* 增加`app,api和插件`composer第三方库支持
+* 增加后台模板动态设置功能
+* 使用`composer classmap`做相关类的映射
+* 更改所有`thinkcmf`包版本号依赖
+* 优化清除缓存,清除opcache缓存
+* 优化`cmf_set_dynamic_config`兼容5.0和5.1
+* 升级`PHPMailer`使用`PHPMailer 6.0`(注意类的引入变化)
+* 修复路由是否存在检测问题
+* 修复url美化由于后台权限设置可能引起的漏洞(漏洞编号CVE-2019-6713 感谢topsec(zhan_ran)的及时反馈)
+* 修复子导航标签报错
+* 修复数据库对象实例化不当导致的问题
+* 修复`BaseController`排序批量更新
+* 修复新建管理员登录时报错
+* 取消`THINKCMF_VERSION`常量,请使用`cmf_version()`
+* 取消`PLUGINS_PATH`常量,请使用`WEB_ROOT.'plugins/`
+
+[swoole]
+* 增加`websocket`演示
+* 优化`swoole`配置初始化
+* 优化`swoole`下内容输出
+* 更改默认缓存大小为128M
+* 修复`swoole`如果控制器返回内容为空报错问题
+* 修复`swoole`下核心包路由注册位置
+* 修复`swoole`下后台风格无法设置
+
+#### 5.1.0-beta
+[核心]
+* 升级`ThinkCMF 5.0`到`ThinkPHP 5.1`
+
+
+
+
+

+ 431 - 0
api/portal/controller/ArticlesController.php

@@ -0,0 +1,431 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use api\user\model\UserFavoriteModel;
+use api\user\model\UserLikeModel;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalPostModel;
+use think\Db;
+
+class ArticlesController extends RestBaseController
+{
+    /**
+     * 文章列表
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params      = $this->request->get();
+        $postService = new PortalPostService();
+        $data        = $postService->postArticles($params);
+        //是否需要关联模型
+        if (!$data->isEmpty()) {
+            if (!empty($params['relation'])) {
+
+                $allowedRelations = allowed_relations(['user', 'categories'], $params['relation']);
+
+                if (!empty($allowedRelations)) {
+                    $data->load('user');
+                    $data->append($allowedRelations);
+                }
+            }
+        }
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取指定的文章
+     * @param $id
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的文章id!');
+        } else {
+            $postModel = new PortalPostModel();
+            $data      = $postModel->where('id', $id)->find();
+
+            if (empty($data)) {
+                $this->error('文章不存在!');
+            } else {
+                $postModel->where('id', $id)->setInc('post_hits');
+                $url         = cmf_url('portal/Article/index', ['id' => $id, 'cid' => $data['categories'][0]['id']], true, true);
+                $data['url'] = $url;
+                $this->success('请求成功!', $data);
+            }
+
+        }
+    }
+
+    /**
+     * 我的文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function my()
+    {
+        $params            = $this->request->get();
+        $params['user_id'] = $this->getUserId();
+
+        $postService = new PortalPostService();
+        $data        = $postService->postArticles($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = [$data];
+        } else {
+            $response = ['list' => $data];
+        }
+
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 添加文章
+     * @throws \think\Exception
+     */
+    public function save()
+    {
+        $data            = $this->request->post();
+        $data['user_id'] = $this->getUserId();
+        $result          = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (empty($data['published_time'])) {
+            $data['published_time'] = time();
+        }
+        $postModel = new PortalPostModel();
+        $postModel->addArticle($data);
+        $this->success('添加成功!');
+    }
+
+    /**
+     * 更新文章
+     * @param $id
+     * @throws \think\Exception
+     */
+    public function update($id)
+    {
+        $data   = $this->request->put();
+        $result = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        $postModel = new PortalPostModel();
+        $res       = $postModel->articleFind(['id' => $id, 'user_id' => $this->getUserId()]);
+        if (empty($res)) {
+            $this->error('文章不存在或者已经删除!');
+        }
+
+        $result = $postModel->editArticle($data, $id, $this->getUserId());
+
+        if ($result === false) {
+            $this->error('编辑失败!');
+        } else {
+            $this->success('编辑成功!');
+        }
+    }
+
+    /**
+     * 删除文章
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function delete($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($id, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 批量删除文章
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function deletes()
+    {
+        $ids = $this->request->post('ids/a');
+        if (empty($ids)) {
+            $this->error('文章id不能为空');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($ids, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 搜索查询
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function search()
+    {
+        $params = $this->request->get();
+
+        if (!empty($params['keyword'])) {
+            $postService = new PortalPostService();
+            $data        = $postService->postArticles($params);
+
+            if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+                $response = $data;
+            } else {
+                $response = ['list' => $data,];
+            }
+            $this->success('请求成功!', $response);
+        } else {
+            $this->error('搜索关键词不能为空!');
+        }
+
+    }
+
+    /**
+     * 文章点赞
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function doLike()
+    {
+        $userId    = $this->getUserId();
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userLikeModel = new UserLikeModel();
+
+        $findLikeCount = $userLikeModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId
+        ])->where('table_name', 'portal_post')->count();
+
+        if (empty($findLikeCount)) {
+            $postModel = new PortalPostModel();
+            $article   = $postModel->where('id', $articleId)->field('id,post_title,post_excerpt,more')->find();
+
+            if (empty($article)) {
+                $this->error('文章不存在!');
+            }
+
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setInc('post_like');
+                $thumbnail = empty($article['more']['thumbnail']) ? '' : $article['more']['thumbnail'];
+                $userLikeModel->insert([
+                    'user_id'     => $userId,
+                    'object_id'   => $articleId,
+                    'table_name'  => 'portal_post',
+                    'title'       => $article['post_title'],
+                    'thumbnail'   => $thumbnail,
+                    'description' => $article['post_excerpt'],
+                    'url'         => json_encode(['action' => 'portal/Article/index', 'param' => ['id' => $articleId, 'cid' => $article['categories'][0]['id']]]),
+                    'create_time' => time()
+                ]);
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('点赞失败!');
+            }
+
+            $likeCount = $postModel->where('id', $articleId)->value('post_like');
+            $this->success("赞好啦!", ['post_like' => $likeCount]);
+        } else {
+            $this->error("您已赞过啦!");
+        }
+    }
+
+    /**
+     * 取消文章点赞
+     */
+    public function cancelLike()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userLikeModel = new UserLikeModel();
+
+        $findLikeCount = $userLikeModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId
+        ])->where('table_name', 'portal_post')->count();
+
+        if (!empty($findLikeCount)) {
+            $postModel = new PortalPostModel();
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setDec('post_like');
+                $userLikeModel->where([
+                    'user_id'   => $userId,
+                    'object_id' => $articleId
+                ])->where('table_name', 'portal_post')->delete();
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('取消点赞失败!');
+            }
+
+            $likeCount = $postModel->where('id', $articleId)->value('post_like');
+            $this->success("取消点赞成功!", ['post_like' => $likeCount]);
+        } else {
+            $this->error("您还没赞过!");
+        }
+    }
+
+    /**
+     * 文章收藏
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function doFavorite()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userFavoriteModel = new UserFavoriteModel();
+
+        $findFavoriteCount = $userFavoriteModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId
+        ])->where('table_name', 'portal_post')->count();
+
+        if (empty($findFavoriteCount)) {
+            $postModel = new PortalPostModel();
+            $article   = $postModel->where(['id' => $articleId])->field('id,post_title,post_excerpt,more')->find();
+            if (empty($article)) {
+                $this->error('文章不存在!');
+            }
+
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setInc('post_favorites');
+                $thumbnail = empty($article['more']['thumbnail']) ? '' : $article['more']['thumbnail'];
+                $userFavoriteModel->insert([
+                    'user_id'     => $userId,
+                    'object_id'   => $articleId,
+                    'table_name'  => 'portal_post',
+                    'thumbnail'   => $thumbnail,
+                    'title'       => $article['post_title'],
+                    'description' => $article['post_excerpt'],
+                    'url'         => json_encode(['action' => 'portal/Article/index', 'param' => ['id' => $articleId, 'cid' => $article['categories'][0]['id']]]),
+                    'create_time' => time()
+                ]);
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+
+                $this->error('收藏失败!');
+            }
+
+            $favoriteCount = $postModel->where('id', $articleId)->value('post_favorites');
+            $this->success("收藏好啦!", ['post_favorites' => $favoriteCount]);
+        } else {
+            $this->error("您已收藏过啦!");
+        }
+    }
+
+    /**
+     * 取消文章收藏
+     */
+    public function cancelFavorite()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userFavoriteModel = new UserFavoriteModel();
+
+        $findFavoriteCount = $userFavoriteModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId
+        ])->where('table_name', 'portal_post')->count();
+
+        if (!empty($findFavoriteCount)) {
+            $postModel = new PortalPostModel();
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setDec('post_favorites');
+                $userFavoriteModel->where([
+                    'user_id'   => $userId,
+                    'object_id' => $articleId
+                ])->where('table_name', 'portal_post')->delete();
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('取消失败!');
+            }
+
+            $favoriteCount = $postModel->where('id', $articleId)->value('post_favorites');
+            $this->success("取消成功!", ['post_favorites' => $favoriteCount]);
+        } else {
+            $this->error("您还没收藏过!");
+        }
+    }
+
+
+    /**
+     * 相关文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function relatedArticles()
+    {
+        $articleId  = $this->request->param('id', 0, 'intval');
+        $categoryId = Db::name('portal_category_post')->where('post_id', $articleId)->value('category_id');
+
+        $postModel = new PortalPostModel();
+        $articles  = $postModel
+            ->alias('post')
+            ->join('__PORTAL_CATEGORY_POST__ category_post', 'post.id=category_post.post_id')
+            ->where(['post.delete_time' => 0, 'post.post_status' => 1, 'category_post.category_id' => $categoryId])
+            ->orderRaw('rand()')
+            ->limit(5)
+            ->select();
+        if ($articles->isEmpty()){
+            $this->error('没有相关文章!');
+        }
+        $this->success('success', ['list' => $articles]);
+    }
+}

+ 81 - 0
api/portal/controller/CategoriesController.php

@@ -0,0 +1,81 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalCategoryService;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalCategoryModel;
+
+class CategoriesController extends RestBaseController
+{
+    /**
+     * 获取分类列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params          = $this->request->get();
+        $categoryService = new PortalCategoryService();
+        $data            = $categoryService->categories($params);
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data];
+        }
+
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 显示指定的分类
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+        $categoryModel = new PortalCategoryModel();
+        $data          = $categoryModel
+            ->where('delete_time', 0)
+            ->where('status', 1)
+            ->where('id', $id)
+            ->find();
+        if ($data) {
+            $this->success('请求成功!', $data);
+        } else {
+            $this->error('该分类不存在!');
+        }
+
+    }
+
+    /**
+     * 获取指定分类的子分类列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function subCategories()
+    {
+        $id = $this->request->get('category_id', 0, 'intval');
+
+        $categoryModel = new PortalCategoryModel();
+        $categories    = $categoryModel->where(['parent_id' => $id])->select();
+        if (!$categories->isEmpty()) {
+            $this->success('请求成功', ['categories' => $categories]);
+        } else {
+            $this->error('该分类下没有子分类!');
+        }
+
+
+    }
+}

+ 76 - 0
api/portal/controller/IndexController.php

@@ -0,0 +1,76 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalPostModel;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalTagModel;
+
+class IndexController extends RestBaseController
+{
+    protected $tagModel;
+
+    /**
+     * 获取标签列表
+     */
+    public function index()
+    {
+        $this->success('请求成功!', "DD");
+    }
+
+    /**
+     * 获取热门标签列表
+     */
+    public function hotTags()
+    {
+        $params                         = $this->request->get();
+        $params['where']['recommended'] = 1;
+        $data                           = $this->tagModel->getDatas($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取标签文章列表
+     * @param int $id
+     */
+    public function articles($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的标签id!');
+        } else {
+            $params    = $this->request->param();
+            $postModel = new PortalPostModel();
+
+            unset($params['id']);
+
+            $articles = $postModel->paramsFilter($params)->alias('post')
+                ->join('__PORTAL_TAG_POST__ tag_post', 'post.id = tag_post.post_id')
+                ->where(['tag_post.tag_id' => $id])->select();
+
+            if (!empty($params['relation'])) {
+                $allowedRelations = $postModel->allowedRelations($params['relation']);
+                if (!empty($allowedRelations)) {
+                    if (count($articles) > 0) {
+                        $articles->load($allowedRelations);
+                        $articles->append($allowedRelations);
+                    }
+                }
+            }
+
+
+            $this->success('请求成功!', ['articles' => $articles]);
+        }
+    }
+}

+ 79 - 0
api/portal/controller/ListsController.php

@@ -0,0 +1,79 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalCategoryModel;
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+
+class ListsController extends RestBaseController
+{
+
+    /**
+     * 推荐文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function recommended()
+    {
+        $param                = $this->request->param();
+        $param['recommended'] = true;
+
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        //是否需要关联模型
+        if (!$articles->isEmpty()) {
+            if (!empty($param['relation'])) {
+                $allowedRelations = allowed_relations(['user', 'categories'], $param['relation']);
+                if (!empty($allowedRelations)) {
+                    $articles->load($allowedRelations);
+                    $articles->append($allowedRelations);
+                }
+            }
+        }
+        $this->success('ok', ['list' => $articles]);
+    }
+
+    /**
+     * 分类文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function getCategoryPostLists()
+    {
+        $categoryId = $this->request->param('category_id', 0, 'intval');
+
+        $portalCategoryModel = new  PortalCategoryModel();
+        $findCategory        = $portalCategoryModel->where('id', $categoryId)->find();
+
+        //分类是否存在
+        if (empty($findCategory)) {
+            $this->error('分类不存在!');
+        }
+
+        $param = $this->request->param();
+
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        //是否需要关联模型
+        if (!$articles->isEmpty()) {
+            if (!empty($param['relation'])) {
+                $allowedRelations = allowed_relations(['user', 'categories'], $param['relation']);
+                if (!empty($allowedRelations)) {
+                    $articles->load($allowedRelations);
+                    $articles->append($allowedRelations);
+                }
+            }
+        }
+        $this->success('ok', ['list' => $articles]);
+    }
+
+}

+ 65 - 0
api/portal/controller/PagesController.php

@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalPostModel;
+
+class PagesController extends RestBaseController
+{
+    /**
+     * 页面列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params      = $this->request->get();
+        $postService = new PortalPostService();
+        $data        = $postService->postArticles($params, true);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取页面
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+
+        $params    = $this->request->get();
+        $field     = empty($params['field']) ? '*' : $params['field'];
+        $postModel = new PortalPostModel();
+        $data      = $postModel
+            ->field($field)
+            ->where('id', $id)
+            ->where('delete_time', 0)
+            ->where('post_status', 1)
+            ->where('post_type', 2)
+            ->find();
+        if ($data){
+            $this->success('请求成功!', $data);
+        }else{
+            $this->error('文章不存在!');
+        }
+
+    }
+}

+ 86 - 0
api/portal/controller/TagsController.php

@@ -0,0 +1,86 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalTagModel;
+use api\portal\service\PortalTagService;
+use cmf\controller\RestBaseController;
+use think\db\Query;
+
+class TagsController extends RestBaseController
+{
+
+    /**
+     * 获取标签列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params     = $this->request->get();
+        $tagService = new PortalTagService();
+        $data       = $tagService->tagList($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        if ($data->isEmpty()) {
+            $this->error('没有标签!');
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取热门标签列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function hotTags()
+    {
+        $params                         = $this->request->get();
+        $params['where']['recommended'] = 1;
+
+        $tagService = new PortalTagService();
+        $data       = $tagService->tagList($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取标签文章列表
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articles($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的标签id!');
+        } else {
+            $filter       = $this->request->param();
+            $filter['id'] = $id;
+            $tagService   = new PortalTagService();
+            $tag          = $tagService->portalTagArticles($filter);
+            if ($tag) {
+                $this->error('没有相关文章');
+            }
+            $this->success('请求成功!', $tag);
+        }
+    }
+}

+ 140 - 0
api/portal/controller/UserArticlesController.php

@@ -0,0 +1,140 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use cmf\controller\RestUserBaseController;
+use api\portal\logic\PortalPostModel;
+
+class UserArticlesController extends RestUserBaseController
+{
+    /**
+     * 显示资源列表
+     */
+    public function index()
+    {
+        $params    = $this->request->get();
+        $userId    = $this->getUserId();
+        $postModel = new PortalPostModel();
+        $dates     = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $dates);
+    }
+
+    /**
+     * 保存新建的资源
+     */
+    public function save()
+    {
+        $dates            = $this->request->post();
+        $dates['user_id'] = $this->getUserId();
+        $result           = $this->validate($dates, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        if (empty($dates['published_time'])) {
+            $dates['published_time'] = time();
+        }
+        $postModel = new PortalPostModel();
+        $postModel->addArticle($dates);
+        $this->success('添加成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     * @param $id
+     */
+    public function read($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $params       = $this->request->get();
+        $params['id'] = $id;
+        $userId       = $this->getUserId();
+        $postModel    = new PortalPostModel();
+        $dates        = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $dates);
+    }
+
+    /**
+     * 保存更新的资源
+     * @param $id
+     */
+    public function update($id)
+    {
+        $data   = $this->request->put();
+        $result = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->editArticle($data, $id, $this->getUserId());
+        if ($result === false) {
+            $this->error('编辑失败!');
+        } else {
+            $this->success('编辑成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     * @param $id
+     */
+    public function delete($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($id, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 批量删除文章
+     */
+    public function deletes()
+    {
+        $ids = $this->request->post('ids/a');
+        if (empty($ids)) {
+            $this->error('文章id不能为空');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($ids, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 我的文章列表
+     */
+    public function my()
+    {
+        $params    = $this->request->get();
+        $userId    = $this->getUserId();
+        $postModel = new PortalPostModel();
+        $data      = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $data);
+    }
+}

+ 41 - 0
api/portal/controller/UserController.php

@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+
+class UserController extends RestBaseController
+{
+    /**
+     * 会员文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articles()
+    {
+        $userId = $this->request->param('user_id', 0, 'intval');
+
+        if (empty($userId)) {
+            $this->error('用户id不能空!');
+        }
+
+        $param             = $this->request->param();
+        $param['user_id']  = $userId;
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        if ($articles->isEmpty()) {
+            $this->error('没有数据');
+        } else {
+            $this->success('ok', ['list' => $articles]);
+        }
+    }
+
+}

+ 324 - 0
api/portal/logic/PortalPostModel.php

@@ -0,0 +1,324 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\logic;
+
+use api\portal\model\PortalPostModel as PortalPost;
+use think\Db;
+class PortalPostModel extends PortalPost
+{
+    /**
+     * 获取相关文章
+     * @param int|string|array $postIds 文章id
+     * @return array
+     */
+    public function getRelationPosts($postIds)
+    {
+        $posts = $this->with('articleUser')
+            ->field('id,post_title,user_id,is_top,post_hits,post_like,comment_count,more')
+            ->whereIn('id', $postIds)
+            ->select();
+        foreach ($posts as $post) {
+            $post->appendRelationAttr('articleUser', 'user_nickname');
+        }
+        return $posts;
+    }
+    /**
+     * 获取用户文章
+     */
+    public function getUserArticles($userId, $params)
+    {
+        $where = [
+            'post_type' => 1,
+            'user_id'   => $userId
+        ];
+        if (!empty($params)) {
+            $this->paramsFilter($params);
+        }
+        return $this->where($where)->select();
+    }
+
+    /**
+     * 会员添加文章
+     * @param array $data 文章数据
+     * @return $this
+     */
+    public function addArticle($data)
+    {
+    	//设置图片附件,写入字段过滤
+    	$dataField  =   $this->setMoreField($data);
+    	$data       =   $dataField[0];
+	    array_push($dataField[1],'user_id');
+	    $this->readonly = array_diff(['user_id'],$this->readonly);
+        $this->allowField($dataField[1])->data($data, true)->isUpdate(false)->save();
+        $categories = $this->strToArr($data['categories']);
+        $this->categories()->attach($categories);
+        if (!empty($data['post_keywords']) && is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+            $this->addTags($keywords, $this->id);
+        }
+        return $this;
+    }
+
+    /**
+     * 会员文章编辑
+     * @param array $data 文章数据
+     * @param int $id 文章id
+     * @param int $userId 文章所属用户id [可选]
+     * @return boolean   成功 true 失败 false
+     */
+    public function editArticle($data, $id, $userId = '')
+    {
+        if (!empty($userId)) {
+            $isBelong = $this->isuserPost($id, $userId);
+            if ($isBelong === false) {
+                return $isBelong;
+            }
+        }
+	    //设置图片附件,写入字段过滤
+	    $dataField             = $this->setMoreField($data);
+        $data                  = $dataField[0];
+        $data['id']            = $id;
+        $this->allowField($dataField[1])->data($data, true)->isUpdate(true)->save();
+
+        $categories            = $this->strToArr($data['categories']);
+        $oldCategoryIds        = $this->categories()->column('category_id');
+        $sameCategoryIds       = array_intersect($categories, $oldCategoryIds);
+        $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
+        $newCategoryIds        = array_diff($categories, $sameCategoryIds);
+        if (!empty($needDeleteCategoryIds)) {
+            $this->categories()->detach($needDeleteCategoryIds);
+        }
+        if (!empty($newCategoryIds)) {
+            $this->categories()->attach(array_values($newCategoryIds));
+        }
+        if (!isset($data['post_keywords'])) {
+	        $keywords = [];
+        } elseif (is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+        }
+        $this->addTags($keywords, $data['id']);
+        return $this;
+    }
+
+    /**
+     * 根据文章关键字,增加标签
+     * @param array $keywords 文章关键字数组
+     * @param int $articleId 文章id
+     * @return void
+     */
+    public function addTags($keywords, $articleId)
+    {
+        foreach ($keywords as $key => $value) {
+            $keywords[$key] = trim($value);
+        }
+        $continue = true;
+        $names    = $this->tags()->column('name');
+        if (!empty($keywords) || !empty($names)) {
+            if (!empty($names)) {
+                $sameNames         = array_intersect($keywords, $names);
+                $keywords          = array_diff($keywords, $sameNames);
+                $shouldDeleteNames = array_diff($names, $sameNames);
+                if (!empty($shouldDeleteNames)) {
+                    $tagIdNames = $this->tags()
+                        ->where('name', 'in', $shouldDeleteNames)
+                        ->column('pivot.id', 'tag_id');
+                    $tagIds     = array_keys($tagIdNames);
+                    $tagPostIds = array_values($tagIdNames);
+                    $tagPosts   = DB::name('portal_tag_post')->where('tag_id', 'in', $tagIds)
+                        ->field('id,tag_id,post_id')
+                        ->select();
+                    $keepTagIds = [];
+                    foreach ($tagPosts as $key => $tagPost) {
+                        if ($articleId != $tagPost['post_id']) {
+                            array_push($keepTagIds, $tagPost['tag_id']);
+                        }
+                    }
+                    $keepTagIds         = array_unique($keepTagIds);
+                    $shouldDeleteTagIds = array_diff($tagIds, $keepTagIds);
+                    DB::name('PortalTag')->delete($shouldDeleteTagIds);
+                    DB::name('PortalTagPost')->delete($tagPostIds);
+                }
+            } else {
+                $tagIdNames = DB::name('portal_tag')->where('name', 'in', $keywords)->column('name', 'id');
+                if (!empty($tagIdNames)) {
+                    $tagIds = array_keys($tagIdNames);
+                    $this->tags()->attach($tagIds);
+                    $keywords = array_diff($keywords, array_values($tagIdNames));
+                    if (empty($keywords)) {
+                        $continue = false;
+                    }
+                }
+            }
+            if ($continue) {
+                foreach ($keywords as $key => $value) {
+                    if (!empty($value)) {
+                        $this->tags()->attach(['name' => $value]);
+                    }
+                }
+            }
+        }
+    }
+
+	/**
+	 * 设置缩略图,图片,附件
+	 * 懒人方法
+	 * @param $data 表单数据
+	 */
+	public function setMoreField($data)
+	{
+		$allowField = [
+			'post_title','post_keywords','post_source',
+			'post_excerpt','post_content','more',
+			'published_time'
+		];
+		if (!empty($data['more'])) {
+			$data['more'] = $this->setMoreUrl($data['more']);
+		}
+		if (!empty($data['thumbnail'])) {
+			$data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+		}
+		return [$data,$allowField];
+	}
+
+    /**
+     * 获取图片附件url相对地址
+     * 默认上传名字 *_names  地址 *_urls
+     * @param $annex 上传附件
+     * @return array
+     */
+    public function setMoreUrl($annex)
+    {
+        $more = [];
+        if (!empty($annex)) {
+            foreach ($annex as $key => $value) {
+                $nameArr = $key . '_names';
+                $urlArr  = $key . '_urls';
+                if (is_string($value[$nameArr]) && is_string($value[$urlArr])) {
+                    $more[$key] = [$value[$nameArr], $value[$urlArr]];
+                } elseif (!empty($value[$nameArr]) && !empty($value[$urlArr])) {
+                    $more[$key] = [];
+                    foreach ($value[$urlArr] as $k => $url) {
+                        $url = cmf_asset_relative_url($url);
+                        array_push($more[$key], ['url' => $url, 'name' => $value[$nameArr][$k]]);
+                    }
+                }
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 删除文章
+     * @param $ids  int|array   文章id
+     * @param int $userId 文章所属用户id  [可选]
+     * @return bool|int 删除结果  true 成功 false 失败  -1 文章不存在
+     */
+    public function deleteArticle($ids, $userId)
+    {
+        $time   = time();
+        $result = false;
+        $where  = [];
+
+        if (!empty($userId)) {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    if ($this->isUserPost($ids, $userId) || $userId == 1) {
+                        $where['id'] = $ids;
+                    }
+                }
+            } else {
+                $ids      = $this->strToArr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $deleteIds = $this->isUserPosts($ids, $userId);
+                    if (!empty($deleteIds)) {
+                        $where['id'] = ['in', $deleteIds];
+                    }
+                }
+            }
+        } else {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    $where['id'] = $ids;
+                }
+            } else {
+                $ids      = $this->strToArr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $where['id'] = ['in', $ids];
+                }
+            }
+        }
+        if (empty($article) && empty($articles)) {
+            return -1;
+        }
+        if (!empty($where)) {
+            $result = $this->useGlobalScope(false)
+                ->where($where)
+                ->setField('delete_time', $time);
+        }
+        if ($result) {
+            $data = [
+                'create_time' => $time,
+                'table_name'  => 'portal_post'
+            ];
+            if (!empty($article)) {
+                $data['name'] = $article['post_title'];
+                $article->recycleBin()->save($data);
+            }
+
+            if (!empty($articles)) {
+                foreach ($articles as $article) {
+                    $data['name'] = $article['post_title'];
+                    $article->recycleBin()->save($data);
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 判断文章所属用户是否为当前用户,超级管理员除外
+     * @params  int $id     文章id
+     * @param   int $userId 当前用户id
+     * @return  boolean     是 true , 否 false
+     */
+    public function isUserPost($id, $userId)
+    {
+        $postUserId = $this->useGlobalScope(false)
+            ->getFieldById($id, 'user_id');
+        if ($postUserId != $userId || $userId != 1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 过滤属于当前用户的文章,超级管理员除外
+     * @params  array $ids     文章id的数组
+     * @param   int $userId 当前用户id
+     * @return  array     属于当前用户的文章id
+     */
+    public function isUserPosts($ids, $userId)
+    {
+        $postIds = $this->useGlobalScope(false)
+            ->where('user_id', $userId)
+            ->where('id', 'in', $ids)
+            ->column('id');
+        return array_intersect($ids, $postIds);
+    }
+}

+ 82 - 0
api/portal/model/PortalCategoryModel.php

@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalCategoryModel extends Model
+{
+    //类型转换
+    protected $type = [
+        'more' => 'array',
+    ];
+
+
+    //模型关联方法
+    protected $relationFilter = ['articles'];
+
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getMoreAttr($value)
+    {
+        $more = json_decode($value, true);
+        if (!empty($more['thumbnail'])) {
+            $more['thumbnail'] = cmf_get_image_url($more['thumbnail']);
+        }
+
+        if (!empty($more['photos'])) {
+            foreach ($more['photos'] as $key => $value) {
+                $more['photos'][$key]['url'] = cmf_get_image_url($value['url']);
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 关联文章表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function articles()
+    {
+        return $this->belongsToMany('PortalPostModel', 'portal_category_post', 'post_id', 'category_id');
+    }
+
+    /**
+     * 关联文章分类和文章表
+     * @return \think\model\relation\HasMany
+     */
+    public function PostIds()
+    {
+        return $this->hasMany('PortalCategoryPostModel', 'category_id', 'id');
+    }
+
+    /**
+     * 此类文章id数组
+     * @param string $category_id 分类di
+     * @return array|string|Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public static function categoryPostIds($category_id)
+    {
+        $ids      = [];
+        $post_ids = self::relation('PostIds')->field(true)->where('id', $category_id)->find();
+        foreach ($post_ids['PostIds'] as $key => $id) {
+            $ids[] = $id['post_id'];
+        }
+        $post_ids['PostIds'] = $ids;
+        return $post_ids;
+    }
+}

+ 24 - 0
api/portal/model/PortalCategoryPostModel.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalCategoryPostModel extends Model
+{
+    /**
+     * 基础查询
+     */
+    protected function base($query)
+    {
+        $query->where('status', 1);
+    }
+}

+ 491 - 0
api/portal/model/PortalPostModel.php

@@ -0,0 +1,491 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Db;
+use think\db\Query;
+use think\Model;
+
+/**
+ * @method getFieldById($id, $string)
+ * @property mixed id
+ */
+class PortalPostModel extends Model
+{
+    //设置只读字段
+    protected $readonly = ['user_id'];
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = true;
+
+    //类型转换
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    /**
+     *  关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function user()
+    {
+        return $this->belongsTo('api\portal\model\UserModel', 'user_id');
+    }
+
+    /**
+     * 关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function articleUser()
+    {
+        return $this->belongsTo('api\portal\model\UserModel', 'user_id')->field('id,user_nickname');
+    }
+
+    /**
+     * 关联分类表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function categories()
+    {
+        return $this->belongsToMany('api\portal\model\PortalCategoryModel', 'portal_category_post', 'category_id', 'post_id');
+    }
+
+    /**
+     * 关联标签表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function tags()
+    {
+        return $this->belongsToMany('api\portal\model\PortalTagModel', 'portal_tag_post', 'tag_id', 'post_id');
+    }
+
+    /**
+     * 关联 回收站 表
+     * @return \think\model\relation\HasOne
+     */
+    public function recycleBin()
+    {
+        return $this->hasOne('api\portal\model\RecycleBinModel', 'object_id');
+    }
+
+    /**
+     * published_time   自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPublishedTimeAttr($value)
+    {
+        // 兼容老版本 1.0.0的客户端
+        $apiVersion = request()->header('XX-Api-Version');
+        if (empty($apiVersion)) {
+            return date('Y-m-d H:i:s', $value);
+        } else {
+            return $value;
+        }
+    }
+
+    /**
+     * published_time   自动转化
+     * @param $value
+     * @return int
+     */
+    public function setPublishedTimeAttr($value)
+    {
+        if (is_numeric($value)) {
+            return $value;
+        }
+        return strtotime($value);
+    }
+
+    public function getPostTitleAttr($value)
+    {
+        return htmlspecialchars_decode($value);
+    }
+
+    public function getPostExcerptAttr($value)
+    {
+        return htmlspecialchars_decode($value);
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPostContentAttr($value)
+    {
+        return cmf_replace_content_file_url(htmlspecialchars_decode($value));
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function setPostContentAttr($value)
+    {
+        return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
+    }
+
+    /**
+     * Thumbnail 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getThumbnailAttr($value)
+    {
+        return cmf_get_image_url($value);
+    }
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getMoreAttr($value)
+    {
+        $more = json_decode($value, true);
+        if (!empty($more['thumbnail'])) {
+            $more['thumbnail'] = cmf_get_image_url($more['thumbnail']);
+        }
+
+        if (!empty($more['audio'])) {
+            $more['audio'] = cmf_get_file_download_url($more['audio']);
+        }
+
+        if (!empty($more['video'])) {
+            $more['video'] = cmf_get_file_download_url($more['video']);
+        }
+
+        if (!empty($more['photos'])) {
+            foreach ($more['photos'] as $key => $value) {
+                $more['photos'][$key]['url'] = cmf_get_image_url($value['url']);
+            }
+        }
+
+        if (!empty($more['files'])) {
+            foreach ($more['files'] as $key => $value) {
+                $more['files'][$key]['url'] = cmf_get_file_download_url($value['url']);
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 文章查询
+     * @param array $filter 数据
+     * @return array|\PDOStatement|string|Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articleFind($filter)
+    {
+        $result = $this
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['id'])) {
+                    $query->where('id', $filter['id']);
+                }
+                if (!empty($filter['user_id'])) {
+                    $query->where('user_id', $filter['user_id']);
+                }
+            })
+            ->where('delete_time', 0)
+            ->where('post_status', 1)
+            ->where('post_type', 1)
+            ->find();
+        return $result;
+    }
+
+    /**
+     * 会员添加文章
+     * @param array $data 文章数据
+     * @return $this
+     * @throws \think\Exception
+     */
+    public function addArticle($data)
+    {
+        if (!empty($data['more'])) {
+            $data['more'] = $this->setMoreUrl($data['more']);
+        }
+        if (!empty($data['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+        }
+        $this->allowField(true)->data($data, true)->isUpdate(false)->save();
+        $categories = str_to_arr($data['categories']);
+        //TODO 无法录入多个分类
+        $this->categories()->attach($categories);
+        if (!empty($data['post_keywords']) && is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+            $this->addTags($keywords, $this->id);
+        }
+        return $this;
+    }
+
+    /**
+     * 会员文章编辑
+     * @param array  $data   文章数据
+     * @param int    $id     文章id
+     * @param string $userId 文章所属用户id [可选]
+     * @return PortalPostModel|bool
+     * @throws \think\Exception
+     */
+    public function editArticle($data, $id, $userId = '')
+    {
+
+        if (!empty($userId)) {
+            //判断是否属于当前用户的文章
+            $isBelong = $this->isuserPost($id, $userId);
+            if ($isBelong === false) {
+                return $isBelong;
+            }
+        }
+
+        if (!empty($data['more'])) {
+            $data['more'] = $this->setMoreUrl($data['more']);
+        }
+        if (!empty($data['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+        }
+        $data['id'] = $id;
+//        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+//        $data['is_top']      = empty($data['is_top']) ? 0 : 1;
+//        $data['recommended'] = empty($data['recommended']) ? 0 : 1;
+        $this->allowField(true)->data($data, true)->isUpdate(true)->save();
+
+        $categories     = str_to_arr($data['categories']);
+        $oldCategoryIds = $this->categories()->column('category_id');
+
+        $sameCategoryIds       = array_intersect($categories, $oldCategoryIds);
+        $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
+        $newCategoryIds        = array_diff($categories, $sameCategoryIds);
+        if (!empty($needDeleteCategoryIds)) {
+            $this->categories()->detach($needDeleteCategoryIds);
+        }
+
+        if (!empty($newCategoryIds)) {
+            $this->categories()->attach(array_values($newCategoryIds));
+        }
+
+        $keywords = [];
+
+        if (!empty($data['post_keywords'])) {
+            if (is_string($data['post_keywords'])) {
+                //加入标签
+                $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+                $keywords              = explode(',', $data['post_keywords']);
+            }
+        }
+
+        $this->addTags($keywords, $data['id']);
+
+        return $this;
+    }
+
+    /**
+     * 根据文章关键字,增加标签
+     * @param array $keywords  文章关键字数组
+     * @param int   $articleId 文章id
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function addTags($keywords, $articleId)
+    {
+        foreach ($keywords as $key => $value) {
+            $keywords[$key] = trim($value);
+        }
+        $continue = true;
+        $names    = $this->tags()->column('name');
+        if (!empty($keywords) || !empty($names)) {
+            if (!empty($names)) {
+                $sameNames         = array_intersect($keywords, $names);
+                $keywords          = array_diff($keywords, $sameNames);
+                $shouldDeleteNames = array_diff($names, $sameNames);
+                if (!empty($shouldDeleteNames)) {
+                    $tagIdNames = $this->tags()
+                        ->where('name', 'in', $shouldDeleteNames)
+                        ->column('pivot.id', 'tag_id');
+                    $tagIds     = array_keys($tagIdNames);
+                    $tagPostIds = array_values($tagIdNames);
+                    $tagPosts   = DB::name('portal_tag_post')
+                        ->where('tag_id', 'in', $tagIds)
+                        ->field('id,tag_id,post_id')
+                        ->select();
+                    $keepTagIds = [];
+                    foreach ($tagPosts as $key => $tagPost) {
+                        if ($articleId != $tagPost['post_id']) {
+                            array_push($keepTagIds, $tagPost['tag_id']);
+                        }
+                    }
+                    $keepTagIds         = array_unique($keepTagIds);
+                    $shouldDeleteTagIds = array_diff($tagIds, $keepTagIds);
+                    Db::name('PortalTag')->delete($shouldDeleteTagIds);
+                    Db::name('PortalTagPost')->delete($tagPostIds);
+                }
+            } else {
+                $tagIdNames = DB::name('portal_tag')->where('name', 'in', $keywords)->column('name', 'id');
+                if (!empty($tagIdNames)) {
+                    $tagIds = array_keys($tagIdNames);
+                    $this->tags()->attach($tagIds);
+                    $keywords = array_diff($keywords, array_values($tagIdNames));
+                    if (empty($keywords)) {
+                        $continue = false;
+                    }
+                }
+            }
+            if ($continue) {
+                foreach ($keywords as $key => $value) {
+                    if (!empty($value)) {
+                        $this->tags()->attach(['name' => $value]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取图片附件url相对地址
+     * 默认上传名字 *_names  地址 *_urls
+     * @param array $annex 上传附件
+     * @return array
+     */
+    public function setMoreUrl($annex)
+    {
+        $more = [];
+        if (!empty($annex)) {
+            foreach ($annex as $key => $value) {
+                $nameArr = $key . '_names';
+                $urlArr  = $key . '_urls';
+                if (is_string($value[$nameArr]) && is_string($value[$urlArr])) {
+                    $more[$key] = [$value[$nameArr], $value[$urlArr]];
+                } elseif (!empty($value[$nameArr]) && !empty($value[$urlArr])) {
+                    $more[$key] = [];
+                    foreach ($value[$urlArr] as $k => $url) {
+                        $url = cmf_asset_relative_url($url);
+                        array_push($more[$key], ['url' => $url, 'name' => $value[$nameArr][$k]]);
+                    }
+                }
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 删除文章
+     * @param  int|array $ids    文章id
+     * @param  string    $userId 文章所属用户id  [可选]
+     * @return bool|int 删除结果  true 成功 false 失败  -1 文章不存在
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function deleteArticle($ids, $userId = '')
+    {
+        $time   = time();
+        $result = false;
+        $where  = [];
+
+        if (!empty($userId)) {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    if ($this->isUserPost($ids, $userId) || $userId == 1) {
+                        $where['id'] = $ids;
+                    }
+                }
+            } else {
+                $ids      = str_to_arr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $deleteIds = $this->isUserPosts($ids, $userId);
+                    if (!empty($deleteIds)) {
+                        $where['id'] = ['in', $deleteIds];
+                    }
+                }
+            }
+        } else {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    $where['id'] = $ids;
+                }
+            } else {
+                $ids      = str_to_arr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $where['id'] = ['in', $ids];
+                }
+            }
+        }
+        if (empty($article) && empty($articles)) {
+            return -1;
+        }
+        if (!empty($where)) {
+            $result = $this->useGlobalScope(false)
+                ->where($where)
+                ->setField('delete_time', $time);
+        }
+        if ($result) {
+            $data = [
+                'create_time' => $time,
+                'table_name'  => 'portal_post'
+            ];
+            if (!empty($article)) {
+                $data['name'] = $article['post_title'];
+                $article->recycleBin()->save($data);
+            }
+
+            if (!empty($articles)) {
+                foreach ($articles as $article) {
+                    $data['name'] = $article['post_title'];
+                    $article->recycleBin()->save($data);
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 判断文章所属用户是否为当前用户,超级管理员除外
+     * @param   int $id     文章id
+     * @param   int $userId 当前用户id
+     * @return  boolean     是 true , 否 false
+     */
+    public function isUserPost($id, $userId)
+    {
+        $postUserId = $this->getFieldById($id, 'user_id');
+        if ($postUserId == $userId || $userId == 1) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 过滤属于当前用户的文章,超级管理员除外
+     * @param   array $ids    文章id的数组
+     * @param   int   $userId 当前用户id
+     * @return  array     属于当前用户的文章id
+     */
+    public function isUserPosts($ids, $userId)
+    {
+        $postIds = $this
+            ->useGlobalScope(false)
+            ->where('user_id', $userId)
+            ->where('id', 'in', $ids)
+            ->column('id');
+        return array_intersect($ids, $postIds);
+    }
+
+
+}

+ 25 - 0
api/portal/model/PortalTagModel.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalTagModel extends Model
+{
+
+    /**
+     * 关联 文章表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function articles()
+    {
+        return $this->belongsToMany('PortalPostModel','portal_tag_post','post_id','tag_id')->alias('post');
+    }
+}

+ 29 - 0
api/portal/model/PortalTagPostModel.php

@@ -0,0 +1,29 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalTagPostModel extends Model
+{
+    /**
+     * 获取指定id相关的文章id数组
+     * @param $post_id  文章id
+     * @return array    相关的文章id
+     */
+    function getRelationPostIds($post_id)
+    {
+        $tagIds  = $this->where('post_id', $post_id)
+            ->column('tag_id');
+        $postIds = $this->whereIn('tag_id', $tagIds)
+            ->column('post_id');
+        return array_unique($postIds);
+    }
+}

+ 16 - 0
api/portal/model/RecycleBinModel.php

@@ -0,0 +1,16 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Model;
+
+class RecycleBinModel extends Model
+{
+
+}

+ 41 - 0
api/portal/model/UserModel.php

@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+
+use think\Model;
+
+class UserModel extends Model
+{
+
+    //模型关联方法
+    protected $relationFilter = ['user'];
+
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getAvatarAttr($value)
+    {
+        $value = !empty($value) ? cmf_get_image_url($value) : $value;
+        return $value;
+    }
+
+    /**
+     * 关联 user表
+     * @return string 关联数据
+     */
+    public function user()
+    {
+        return $this->belongsTo('UserModel', 'user_id')->setEagerlyType(1);
+    }
+}

+ 22 - 0
api/portal/route.php

@@ -0,0 +1,22 @@
+<?php
+
+use think\facade\Route;
+
+Route::resource('portal/categories', 'portal/Categories');
+Route::get('portal/categories/subCategories', 'portal/Categories/subCategories');
+Route::resource('portal/articles', 'portal/Articles');
+Route::resource('portal/pages', 'portal/Pages');
+Route::resource('portal/userArticles', 'portal/UserArticles');
+
+Route::get('portal/search', 'portal/Articles/search');
+Route::get('portal/articles/my', 'portal/Articles/my');
+Route::get('portal/articles/relatedArticles', 'portal/Articles/relatedArticles');
+Route::post('portal/articles/doLike', 'portal/Articles/doLike');
+Route::post('portal/articles/cancelLike', 'portal/Articles/cancelLike');
+Route::post('portal/articles/doFavorite', 'portal/Articles/doFavorite');
+Route::post('portal/articles/cancelFavorite', 'portal/Articles/cancelFavorite');
+Route::get('portal/tags/:id/articles', 'portal/Tags/articles');
+Route::get('portal/tags', 'portal/Tags/index');
+Route::get('portal/tags/hotTags', 'portal/Tags/hotTags');
+
+Route::post('portal/userArticles/deletes', 'portal/UserArticles/deletes');

+ 59 - 0
api/portal/service/PortalCategoryService.php

@@ -0,0 +1,59 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/08
+// | Time:上午 10:32
+// +----------------------------------------------------------------------
+
+
+namespace api\portal\service;
+
+
+use api\portal\model\PortalCategoryModel;
+use think\db\Query;
+
+class PortalCategoryService
+{
+    /**
+     * @param $filter
+     * @return array|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function categories($filter)
+    {
+        $categoryModel = new PortalCategoryModel();
+        //条件分解
+        $field = empty($filter['field']) ? '*' : $filter['field'];
+        $order = empty($filter['order']) ? ['-id'] : explode(',', $filter['order']);
+        $page  = empty($filter['page']) ? '' : $filter['page'];
+        $limit = empty($filter['limit']) ? '' : $filter['limit'];
+        if (!empty($page)) {
+            $categoryModel = $categoryModel->page($page);
+        } elseif (!empty($limit)) {
+            $categoryModel = $categoryModel->limit($limit);
+        } else {
+            $categoryModel = $categoryModel->limit(10);
+        }
+        //转化-+为desc、asc
+        $orderArr = order_shift($order);
+
+        $result = $categoryModel
+            ->field($field)
+            ->where('delete_time', 0)
+            ->where('status', 1)
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['ids'])) {
+                    $query->where('id', 'in', $filter['ids']);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+        return $result;
+    }
+}

+ 108 - 0
api/portal/service/PortalPostService.php

@@ -0,0 +1,108 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\service;
+
+use api\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PortalPostService
+{
+    //模型关联方法
+    protected $relationFilter = ['user'];
+
+    /**
+     * 文章列表
+     * @param      $filter
+     * @param bool $isPage
+     * @return array|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function postArticles($filter, $isPage = false)
+    {
+        $join = [];
+
+        $field = empty($filter['field']) ? 'a.*' : explode(',', $filter['field']);
+        //转为查询条件
+        if (is_array($field)) {
+            foreach ($field as $key => $value) {
+                $field[$key] = 'a.' . $value;
+            }
+            $field = implode(',', $field);
+        }
+        $page     = empty($filter['page']) ? '' : $filter['page'];
+        $limit    = empty($filter['limit']) ? '' : $filter['limit'];
+        $order    = empty($filter['order']) ? ['-update_time'] : explode(',', $filter['order']);
+        $category = empty($filter['category_id']) ? 0 : intval($filter['category_id']);
+        if (!empty($category)) {
+            array_push($join, ['__PORTAL_CATEGORY_POST__ b', 'a.id = b.post_id']);
+            $field = $field.',b.id AS post_category_id,b.list_order,b.category_id';
+        }
+
+        $orderArr = order_shift($order);
+
+        $portalPostModel = new PortalPostModel();
+
+
+        if (!empty($page)) {
+            $portalPostModel = $portalPostModel->page($page);
+        } elseif (!empty($limit)) {
+            $portalPostModel = $portalPostModel->limit($limit);
+        } else {
+            $portalPostModel = $portalPostModel->limit(10);
+        }
+
+        $articles = $portalPostModel
+            ->alias('a')
+            ->field($field)
+            ->join($join)
+            ->where('a.create_time', '>=', 0)
+            ->where('a.delete_time', 0)
+            ->where('a.post_status', 1)
+            ->where(function (Query $query) use ($filter, $isPage) {
+                if (!empty($filter['user_id'])) {
+                    $query->where('a.user_id', $filter['user_id']);
+                }
+                $category = empty($filter['category_id']) ? 0 : intval($filter['category_id']);
+                if (!empty($category)) {
+                    $query->where('b.category_id', $category);
+                }
+                $startTime = empty($filter['start_time']) ? 0 : strtotime($filter['start_time']);
+                $endTime   = empty($filter['end_time']) ? 0 : strtotime($filter['end_time']);
+                if (!empty($startTime)) {
+                    $query->where('a.published_time', '>=', $startTime);
+                }
+                if (!empty($endTime)) {
+                    $query->where('a.published_time', '<=', $endTime);
+                }
+                $keyword = empty($filter['keyword']) ? '' : $filter['keyword'];
+                if (!empty($keyword)) {
+                    $query->where('a.post_title', 'like', "%$keyword%");
+                }
+                if ($isPage) {
+                    $query->where('a.post_type', 2);
+                } else {
+                    $query->where('a.post_type', 1);
+                }
+                if (!empty($filter['recommended'])) {
+                    $query->where('a.recommended', 1);
+                }
+                if (!empty($filter['ids'])) {
+                    $ids = str_to_arr($filter['ids']);
+                    $query->where('a.id', 'in', $ids);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+
+        return $articles;
+    }
+
+}

+ 100 - 0
api/portal/service/PortalTagService.php

@@ -0,0 +1,100 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/09
+// | Time:下午 06:10
+// +----------------------------------------------------------------------
+
+
+namespace api\portal\service;
+
+
+use api\portal\model\PortalPostModel;
+use api\portal\model\PortalTagModel;
+use think\db\Query;
+
+class PortalTagService
+{
+    /**
+     * 获取标签列表
+     * @param array $filter 参数
+     * @return array|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function tagList($filter)
+    {
+        $field    = empty($filter['field']) ? '*' : $filter['field'];
+        $page     = empty($filter['page']) ? '' : $filter['page'];
+        $limit    = empty($filter['limit']) ? '' : $filter['limit'];
+        $order    = empty($filter['order']) ? ['-id'] : explode(',', $filter['order']);
+        $orderArr = order_shift($order);
+        $tagModel = new PortalTagModel();
+        if (!empty($page)) {
+            $tagModel = $tagModel->page($page);
+        } elseif (!empty($limit)) {
+            $tagModel = $tagModel->limit($limit);
+        } else {
+            $tagModel = $tagModel->limit(10);
+        }
+
+        $result = $tagModel
+            ->field($field)
+            ->where('status', 1)
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['id'])) {
+                    $query->where('id', $filter['id']);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+        return $result;
+    }
+
+    /**
+     * @param $filter
+     * @return array|\PDOStatement|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function portalTagArticles($filter)
+    {
+        $tagModel = new PortalTagModel();
+        $tag      = $tagModel
+            ->with([
+                'articles' => function (Query $query) use ($filter) {
+                    $field = empty($filter['field']) ? 'post.*' : explode(',', $filter['field']);
+                    //转为查询条件
+                    if (is_array($field)) {
+                        foreach ($field as $key => $value) {
+                            $field[$key] = 'post.' . $value;
+                        }
+                        $field = implode(',', $field);
+                    }
+                    $page     = empty($filter['page']) ? '' : $filter['page'];
+                    $limit    = empty($filter['limit']) ? '10' : $filter['limit'];
+                    $order    = empty($filter['order']) ? ['-post.id'] : explode(',', $filter['order']);
+                    $orderArr = order_shift($order);
+                    $query->field($field);
+                    if (!empty($page)) {
+                        $query->page($page);
+                    } elseif (!empty($limit)) {
+                        $query->limit($limit);
+                    } else {
+                        $query->limit(10);
+                    }
+                    $query->order($orderArr);
+                    $query->hidden(['pivot']);
+                }
+            ])
+            ->where('id', $filter['id'])
+            ->find();
+        return $tag;
+    }
+}

+ 34 - 0
api/portal/validate/ArticlesValidate.php

@@ -0,0 +1,34 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/11
+// | Time:下午 03:24
+// +----------------------------------------------------------------------
+namespace api\portal\validate;
+
+use think\Validate;
+
+class ArticlesValidate extends Validate
+{
+    protected $rule = [
+        'post_title'        =>  'require',
+	    'post_content'      =>  'require',
+	    'categories'        =>  'require'
+    ];
+    protected $message = [
+        'post_title.require'    =>  '文章标题不能为空',
+	    'post_content.require'  =>  '内容不能为空',
+	    'categories.require'    =>  '文章分类不能为空'
+    ];
+
+    protected $scene = [
+        'article'  => [ 'post_title' , 'post_content' , 'categories' ],
+        'page' => ['post_title']
+    ];
+}

+ 376 - 0
app/admin/controller/UserController.php

@@ -0,0 +1,376 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\admin\controller;
+
+use cmf\controller\AdminBaseController;
+use think\Db;
+use think\db\Query;
+
+/**
+ * Class UserController
+ * @package app\admin\controller
+ * @adminMenuRoot(
+ *     'name'   => '管理组',
+ *     'action' => 'default',
+ *     'parent' => 'user/AdminIndex/default',
+ *     'display'=> true,
+ *     'order'  => 10000,
+ *     'icon'   => '',
+ *     'remark' => '管理组'
+ * )
+ */
+class UserController extends AdminBaseController
+{
+
+    /**
+     * 管理员列表
+     * @adminMenu(
+     *     'name'   => '管理员',
+     *     'parent' => 'default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员管理',
+     *     'param'  => ''
+     * )
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $content = hook_one('admin_user_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        /**搜索条件**/
+        $userLogin = $this->request->param('user_login');
+        $userEmail = trim($this->request->param('user_email'));
+
+        $users = Db::name('user')
+            ->where('user_type', 1)
+            ->where(function (Query $query) use ($userLogin, $userEmail) {
+                if ($userLogin) {
+                    $query->where('user_login', 'like', "%$userLogin%");
+                }
+
+                if ($userEmail) {
+                    $query->where('user_email', 'like', "%$userEmail%");
+                }
+            })
+            ->order("id DESC")
+            ->paginate(10);
+        $users->appends(['user_login' => $userLogin, 'user_email' => $userEmail]);
+        // 获取分页显示
+        $page = $users->render();
+
+        $rolesSrc = Db::name('role')->select();
+        $roles    = [];
+        foreach ($rolesSrc as $r) {
+            $roleId           = $r['id'];
+            $roles["$roleId"] = $r;
+        }
+        $this->assign("page", $page);
+        $this->assign("roles", $roles);
+        $this->assign("users", $users);
+        return $this->fetch();
+    }
+
+    /**
+     * 管理员添加
+     * @adminMenu(
+     *     'name'   => '管理员添加',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员添加',
+     *     'param'  => ''
+     * )
+     */
+    public function add()
+    {
+        $content = hook_one('admin_user_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $roles = Db::name('role')->where('status', 1)->order("id DESC")->select();
+        $this->assign("roles", $roles);
+        return $this->fetch();
+    }
+
+    /**
+     * 管理员添加提交
+     * @adminMenu(
+     *     'name'   => '管理员添加提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员添加提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            if (!empty($_POST['role_id']) && is_array($_POST['role_id'])) {
+                $role_ids = $_POST['role_id'];
+                unset($_POST['role_id']);
+                $result = $this->validate($this->request->param(), 'User');
+                if ($result !== true) {
+                    $this->error($result);
+                } else {
+                    $_POST['user_pass'] = cmf_password($_POST['user_pass']);
+                    $result             = DB::name('user')->insertGetId($_POST);
+                    if ($result !== false) {
+                        //$role_user_model=M("RoleUser");
+                        foreach ($role_ids as $role_id) {
+                            if (cmf_get_current_admin_id() != 1 && $role_id == 1) {
+                                $this->error("为了网站的安全,非网站创建者不可创建超级管理员!");
+                            }
+                            Db::name('RoleUser')->insert(["role_id" => $role_id, "user_id" => $result]);
+                        }
+                        $this->success("添加成功!", url("user/index"));
+                    } else {
+                        $this->error("添加失败!");
+                    }
+                }
+            } else {
+                $this->error("请为此用户指定角色!");
+            }
+
+        }
+    }
+
+    /**
+     * 管理员编辑
+     * @adminMenu(
+     *     'name'   => '管理员编辑',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员编辑',
+     *     'param'  => ''
+     * )
+     */
+    public function edit()
+    {
+        $content = hook_one('admin_user_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id    = $this->request->param('id', 0, 'intval');
+        $roles = DB::name('role')->where('status', 1)->order("id DESC")->select();
+        $this->assign("roles", $roles);
+        $role_ids = DB::name('RoleUser')->where("user_id", $id)->column("role_id");
+        $this->assign("role_ids", $role_ids);
+
+        $user = DB::name('user')->where("id", $id)->find();
+        $this->assign($user);
+        return $this->fetch();
+    }
+
+    /**
+     * 管理员编辑提交
+     * @adminMenu(
+     *     'name'   => '管理员编辑提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员编辑提交',
+     *     'param'  => ''
+     * )
+     */
+    public function editPost()
+    {
+        if ($this->request->isPost()) {
+            if (!empty($_POST['role_id']) && is_array($_POST['role_id'])) {
+                if (empty($_POST['user_pass'])) {
+                    unset($_POST['user_pass']);
+                } else {
+                    $_POST['user_pass'] = cmf_password($_POST['user_pass']);
+                }
+                $role_ids = $this->request->param('role_id/a');
+                unset($_POST['role_id']);
+                $result = $this->validate($this->request->param(), 'User.edit');
+
+                if ($result !== true) {
+                    // 验证失败 输出错误信息
+                    $this->error($result);
+                } else {
+                    $result = DB::name('user')->update($_POST);
+                    if ($result !== false) {
+                        $uid = $this->request->param('id', 0, 'intval');
+                        DB::name("RoleUser")->where("user_id", $uid)->delete();
+                        foreach ($role_ids as $role_id) {
+                            if (cmf_get_current_admin_id() != 1 && $role_id == 1) {
+                                $this->error("为了网站的安全,非网站创建者不可创建超级管理员!");
+                            }
+                            DB::name("RoleUser")->insert(["role_id" => $role_id, "user_id" => $uid]);
+                        }
+                        $this->success("保存成功!");
+                    } else {
+                        $this->error("保存失败!");
+                    }
+                }
+            } else {
+                $this->error("请为此用户指定角色!");
+            }
+
+        }
+    }
+
+    /**
+     * 管理员个人信息修改
+     * @adminMenu(
+     *     'name'   => '个人信息',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员个人信息修改',
+     *     'param'  => ''
+     * )
+     */
+    public function userInfo()
+    {
+        $id   = cmf_get_current_admin_id();
+        $user = Db::name('user')->where("id", $id)->find();
+        $this->assign($user);
+        return $this->fetch();
+    }
+
+    /**
+     * 管理员个人信息修改提交
+     * @adminMenu(
+     *     'name'   => '管理员个人信息修改提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员个人信息修改提交',
+     *     'param'  => ''
+     * )
+     */
+    public function userInfoPost()
+    {
+        if ($this->request->isPost()) {
+
+            $data             = $this->request->post();
+            $data['birthday'] = strtotime($data['birthday']);
+            $data['id']       = cmf_get_current_admin_id();
+            $create_result    = Db::name('user')->update($data);;
+            if ($create_result !== false) {
+                $this->success("保存成功!");
+            } else {
+                $this->error("保存失败!");
+            }
+        }
+    }
+
+    /**
+     * 管理员删除
+     * @adminMenu(
+     *     'name'   => '管理员删除',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '管理员删除',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id == 1) {
+            $this->error("最高管理员不能删除!");
+        }
+
+        if (Db::name('user')->delete($id) !== false) {
+            Db::name("RoleUser")->where("user_id", $id)->delete();
+            $this->success("删除成功!");
+        } else {
+            $this->error("删除失败!");
+        }
+    }
+
+    /**
+     * 停用管理员
+     * @adminMenu(
+     *     'name'   => '停用管理员',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '停用管理员',
+     *     'param'  => ''
+     * )
+     */
+    public function ban()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if (!empty($id)) {
+            $result = Db::name('user')->where(["id" => $id, "user_type" => 1])->setField('user_status', '0');
+            if ($result !== false) {
+                $this->success("管理员停用成功!", url("user/index"));
+            } else {
+                $this->error('管理员停用失败!');
+            }
+        } else {
+            $this->error('数据传入失败!');
+        }
+    }
+
+    /**
+     * 启用管理员
+     * @adminMenu(
+     *     'name'   => '启用管理员',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '启用管理员',
+     *     'param'  => ''
+     * )
+     */
+    public function cancelBan()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if (!empty($id)) {
+            $result = Db::name('user')->where(["id" => $id, "user_type" => 1])->setField('user_status', '1');
+            if ($result !== false) {
+                $this->success("管理员启用成功!", url("user/index"));
+            } else {
+                $this->error('管理员启用失败!');
+            }
+        } else {
+            $this->error('数据传入失败!');
+        }
+    }
+}

+ 33 - 0
app/admin/validate/UserValidate.php

@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\admin\validate;
+
+use think\Validate;
+
+class UserValidate extends Validate
+{
+    protected $rule = [
+        'user_login' => 'require|unique:user,user_login',
+        'user_pass'  => 'require',
+        'user_email' => 'email',
+    ];
+    protected $message = [
+        'user_login.require' => '用户不能为空',
+        'user_login.unique'  => '用户名已存在',
+        'user_pass.require'  => '密码不能为空',
+        'user_email.unique'  => '邮箱已经存在',
+    ];
+
+    protected $scene = [
+        'add'  => ['user_login', 'user_pass'],
+        'edit' => ['user_login'],
+    ];
+}

+ 67 - 0
app/portal/api/CategoryApi.php

@@ -0,0 +1,67 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\api;
+
+use app\portal\model\PortalCategoryModel;
+use think\db\Query;
+
+class CategoryApi
+{
+    /**
+     * 分类列表 用于模板设计
+     * @param array $param
+     * @return false|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index($param = [])
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = ['delete_time' => 0];
+
+        //返回的数据必须是数据集或数组,item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        return $portalCategoryModel->where($where)
+            ->where(function (Query $query) use ($param) {
+                if (!empty($param['keyword'])) {
+                    $query->where('name', 'like', "%{$param['keyword']}%");
+                }
+            })->select();
+    }
+
+    /**
+     * 分类列表 用于导航选择
+     * @return array
+     */
+    public function nav()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = ['delete_time' => 0];
+
+        $categories = $portalCategoryModel->where($where)->select();
+
+        $return = [
+            //'name'  => '文章分类',
+            'rule'  => [
+                'action' => 'portal/List/index',
+                'param'  => [
+                    'id' => 'id'
+                ]
+            ],//url规则
+            'items' => $categories //每个子项item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        ];
+
+        return $return;
+    }
+
+}

+ 76 - 0
app/portal/api/PageApi.php

@@ -0,0 +1,76 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\api;
+
+use app\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PageApi
+{
+    /**
+     * 页面列表 用于模板设计
+     * @param array $param
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public function index($param = [])
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_type'      => 2,
+            'post_status'    => 1,
+            'delete_time'    => 0
+        ];
+
+        //返回的数据必须是数据集或数组,item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        return $portalPostModel->field('id,post_title AS name')
+            ->where($where)
+            ->where('published_time',['<', time()], ['> time', 0],'and')
+            ->where(function (Query $query) use ($param) {
+                if (!empty($param['keyword'])) {
+                    $query->where('post_title', 'like', "%{$param['keyword']}%");
+                }
+            })->select();
+    }
+
+    /**
+     * 页面列表 用于导航选择
+     * @return array
+     */
+    public function nav()
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_type'      => 2,
+            'post_status'    => 1,
+            'delete_time'    => 0
+        ];
+
+
+        $pages = $portalPostModel->field('id,post_title AS name')
+            ->where('published_time',['<', time()], ['> time', 0],'and')
+            ->where($where)->select();
+
+        $return = [
+            'rule'  => [
+                'action' => 'portal/Page/index',
+                'param'  => [
+                    'id' => 'id'
+                ]
+            ],//url规则
+            'items' => $pages //每个子项item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        ];
+
+        return $return;
+    }
+
+}

+ 460 - 0
app/portal/controller/AdminArticleController.php

@@ -0,0 +1,460 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalPostModel;
+use app\portal\service\PostService;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use app\admin\model\ThemeModel;
+
+class AdminArticleController extends AdminBaseController
+{
+    /**
+     * 文章列表
+     * @adminMenu(
+     *     'name'   => '文章管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章列表',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_article_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $param = $this->request->param();
+
+        $categoryId = $this->request->param('category', 0, 'intval');
+
+        $postService = new PostService();
+        $data        = $postService->adminArticleList($param);
+
+        $data->appends($param);
+
+        $portalCategoryModel = new PortalCategoryModel();
+        $categoryTree        = $portalCategoryModel->adminCategoryTree($categoryId);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('articles', $data->items());
+        $this->assign('category_tree', $categoryTree);
+        $this->assign('category', $categoryId);
+        $this->assign('page', $data->render());
+
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章
+     * @adminMenu(
+     *     'name'   => '添加文章',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_article_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $themeModel        = new ThemeModel();
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+        $this->assign('article_theme_files', $articleThemeFiles);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章提交
+     * @adminMenu(
+     *     'name'   => '添加文章提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+
+            //状态只能设置默认值。未发布、未置顶、未推荐
+            $data['post']['post_status'] = 1;
+            $data['post']['is_top']      = 0;
+            $data['post']['recommended'] = 0;
+
+            $post = $data['post'];
+
+            $result = $this->validate($post, 'AdminArticle');
+            if ($result !== true) {
+                $this->error($result);
+            }
+
+            $portalPostModel = new PortalPostModel();
+
+            if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+                $data['post']['more']['photos'] = [];
+                foreach ($data['photo_urls'] as $key => $url) {
+                    $photoUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+                }
+            }
+
+            if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+                $data['post']['more']['files'] = [];
+                foreach ($data['file_urls'] as $key => $url) {
+                    $fileUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+                }
+            }
+
+
+            $portalPostModel->adminAddArticle($data['post'], $data['post']['categories']);
+
+            $data['post']['id'] = $portalPostModel->id;
+            $hookParam          = [
+                'is_add'  => true,
+                'article' => $data['post']
+            ];
+            hook('portal_admin_after_save_article', $hookParam);
+
+
+            $this->success('添加成功!', url('AdminArticle/edit', ['id' => $portalPostModel->id]));
+        }
+
+    }
+
+    /**
+     * 编辑文章
+     * @adminMenu(
+     *     'name'   => '编辑文章',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function edit()
+    {
+        $content = hook_one('portal_admin_article_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+
+        $portalPostModel = new PortalPostModel();
+        $post            = $portalPostModel->where('id', $id)->find();
+        $postCategories  = $post->categories()->alias('a')->column('a.name', 'a.id');
+        $postCategoryIds = implode(',', array_keys($postCategories));
+
+        $themeModel        = new ThemeModel();
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+        $this->assign('article_theme_files', $articleThemeFiles);
+        $this->assign('post', $post);
+        $this->assign('post_categories', $postCategories);
+        $this->assign('post_category_ids', $postCategoryIds);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑文章提交
+     * @adminMenu(
+     *     'name'   => '编辑文章提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章提交',
+     *     'param'  => ''
+     * )
+     * @throws \think\Exception
+     */
+    public function editPost()
+    {
+
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+
+            //需要抹除发布、置顶、推荐的修改。
+            unset($data['post']['post_status']);
+            unset($data['post']['is_top']);
+            unset($data['post']['recommended']);
+
+            $post   = $data['post'];
+            $result = $this->validate($post, 'AdminArticle');
+            if ($result !== true) {
+                $this->error($result);
+            }
+
+            $portalPostModel = new PortalPostModel();
+
+            if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+                $data['post']['more']['photos'] = [];
+                foreach ($data['photo_urls'] as $key => $url) {
+                    $photoUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+                }
+            }
+
+            if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+                $data['post']['more']['files'] = [];
+                foreach ($data['file_urls'] as $key => $url) {
+                    $fileUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+                }
+            }
+
+            $portalPostModel->adminEditArticle($data['post'], $data['post']['categories']);
+
+            $hookParam = [
+                'is_add'  => false,
+                'article' => $data['post']
+            ];
+            hook('portal_admin_after_save_article', $hookParam);
+
+            $this->success('保存成功!');
+
+        }
+    }
+
+    /**
+     * 文章删除
+     * @adminMenu(
+     *     'name'   => '文章删除',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章删除',
+     *     'param'  => ''
+     * )
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function delete()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['id'])) {
+            $id           = $this->request->param('id', 0, 'intval');
+            $result       = $portalPostModel->where('id', $id)->find();
+            $data         = [
+                'object_id'   => $result['id'],
+                'create_time' => time(),
+                'table_name'  => 'portal_post',
+                'name'        => $result['post_title'],
+                'user_id'     => cmf_get_current_admin_id()
+            ];
+            $resultPortal = $portalPostModel
+                ->where('id', $id)
+                ->update(['delete_time' => time()]);
+            if ($resultPortal) {
+                Db::name('portal_category_post')->where('post_id', $id)->update(['status' => 0]);
+                Db::name('portal_tag_post')->where('post_id', $id)->update(['status' => 0]);
+
+                Db::name('recycleBin')->insert($data);
+            }
+            $this->success("删除成功!", '');
+
+        }
+
+        if (isset($param['ids'])) {
+            $ids     = $this->request->param('ids/a');
+            $recycle = $portalPostModel->where('id', 'in', $ids)->select();
+            $result  = $portalPostModel->where('id', 'in', $ids)->update(['delete_time' => time()]);
+            if ($result) {
+                Db::name('portal_category_post')->where('post_id', 'in', $ids)->update(['status' => 0]);
+                Db::name('portal_tag_post')->where('post_id', 'in', $ids)->update(['status' => 0]);
+                foreach ($recycle as $value) {
+                    $data = [
+                        'object_id'   => $value['id'],
+                        'create_time' => time(),
+                        'table_name'  => 'portal_post',
+                        'name'        => $value['post_title'],
+                        'user_id'     => cmf_get_current_admin_id()
+                    ];
+                    Db::name('recycleBin')->insert($data);
+                }
+                $this->success("删除成功!", '');
+            }
+        }
+    }
+
+    /**
+     * 文章发布
+     * @adminMenu(
+     *     'name'   => '文章发布',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章发布',
+     *     'param'  => ''
+     * )
+     */
+    public function publish()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+            $portalPostModel->where('id', 'in', $ids)->update(['post_status' => 1, 'published_time' => time()]);
+            $this->success("发布成功!", '');
+        }
+
+        if (isset($param['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+            $portalPostModel->where('id', 'in', $ids)->update(['post_status' => 0]);
+            $this->success("取消发布成功!", '');
+        }
+
+    }
+
+    /**
+     * 文章置顶
+     * @adminMenu(
+     *     'name'   => '文章置顶',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章置顶',
+     *     'param'  => ''
+     * )
+     */
+    public function top()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['is_top' => 1]);
+
+            $this->success("置顶成功!", '');
+
+        }
+
+        if (isset($_POST['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['is_top' => 0]);
+
+            $this->success("取消置顶成功!", '');
+        }
+    }
+
+    /**
+     * 文章推荐
+     * @adminMenu(
+     *     'name'   => '文章推荐',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章推荐',
+     *     'param'  => ''
+     * )
+     */
+    public function recommend()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['recommended' => 1]);
+
+            $this->success("推荐成功!", '');
+
+        }
+        if (isset($param['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['recommended' => 0]);
+
+            $this->success("取消推荐成功!", '');
+
+        }
+    }
+
+    /**
+     * 文章排序
+     * @adminMenu(
+     *     'name'   => '文章排序',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章排序',
+     *     'param'  => ''
+     * )
+     */
+    public function listOrder()
+    {
+        parent::listOrders(Db::name('portal_category_post'));
+        $this->success("排序更新成功!", '');
+    }
+}

+ 370 - 0
app/portal/controller/AdminCategoryController.php

@@ -0,0 +1,370 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\admin\model\RouteModel;
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use app\admin\model\ThemeModel;
+
+
+class AdminCategoryController extends AdminBaseController
+{
+    /**
+     * 文章分类列表
+     * @adminMenu(
+     *     'name'   => '分类管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类列表',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_category_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $portalCategoryModel = new PortalCategoryModel();
+        $keyword             = $this->request->param('keyword');
+
+        if (empty($keyword)) {
+            $categoryTree = $portalCategoryModel->adminCategoryTableTree();
+            $this->assign('category_tree', $categoryTree);
+        } else {
+            $categories = $portalCategoryModel->where('name', 'like', "%{$keyword}%")
+                ->where('delete_time', 0)->select();
+            $this->assign('categories', $categories);
+        }
+
+        $this->assign('keyword', $keyword);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章分类
+     * @adminMenu(
+     *     'name'   => '添加文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章分类',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_category_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $parentId            = $this->request->param('parent', 0, 'intval');
+        $portalCategoryModel = new PortalCategoryModel();
+        $categoriesTree      = $portalCategoryModel->adminCategoryTree($parentId);
+
+        $themeModel        = new ThemeModel();
+        $listThemeFiles    = $themeModel->getActionThemeFiles('portal/List/index');
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+
+        $this->assign('list_theme_files', $listThemeFiles);
+        $this->assign('article_theme_files', $articleThemeFiles);
+        $this->assign('categories_tree', $categoriesTree);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章分类提交
+     * @adminMenu(
+     *     'name'   => '添加文章分类提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章分类提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $data = $this->request->param();
+
+        $result = $this->validate($data, 'PortalCategory');
+
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        $result = $portalCategoryModel->addCategory($data);
+
+        if ($result === false) {
+            $this->error('添加失败!');
+        }
+
+        $this->success('添加成功!', url('AdminCategory/index'));
+    }
+
+    /**
+     * 编辑文章分类
+     * @adminMenu(
+     *     'name'   => '编辑文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章分类',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function edit()
+    {
+
+        $content = hook_one('portal_admin_category_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id > 0) {
+            $portalCategoryModel = new PortalCategoryModel();
+            $category            = $portalCategoryModel->get($id)->toArray();
+
+
+            $categoriesTree = $portalCategoryModel->adminCategoryTree($category['parent_id'], $id);
+
+            $themeModel        = new ThemeModel();
+            $listThemeFiles    = $themeModel->getActionThemeFiles('portal/List/index');
+            $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+
+            $routeModel = new RouteModel();
+            $alias      = $routeModel->getUrl('portal/List/index', ['id' => $id]);
+
+            $category['alias'] = $alias;
+            $this->assign($category);
+            $this->assign('list_theme_files', $listThemeFiles);
+            $this->assign('article_theme_files', $articleThemeFiles);
+            $this->assign('categories_tree', $categoriesTree);
+            return $this->fetch();
+        } else {
+            $this->error('操作错误!');
+        }
+
+    }
+
+    /**
+     * 编辑文章分类提交
+     * @adminMenu(
+     *     'name'   => '编辑文章分类提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章分类提交',
+     *     'param'  => ''
+     * )
+     */
+    public function editPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data, 'PortalCategory');
+
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $result = $portalCategoryModel->editCategory($data);
+
+        if ($result === false) {
+            $this->error('保存失败!');
+        }
+
+        $this->success('保存成功!');
+    }
+
+    /**
+     * 文章分类选择对话框
+     * @adminMenu(
+     *     'name'   => '文章分类选择对话框',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类选择对话框',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function select()
+    {
+        $ids                 = $this->request->param('ids');
+        $selectedIds         = explode(',', $ids);
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $tpl = <<<tpl
+<tr class='data-item-tr'>
+    <td>
+        <input type='checkbox' class='js-check' data-yid='js-check-y' data-xid='js-check-x' name='ids[]'
+               value='\$id' data-name='\$name' \$checked>
+    </td>
+    <td>\$id</td>
+    <td>\$spacer <a href='\$url' target='_blank'>\$name</a></td>
+</tr>
+tpl;
+
+        $categoryTree = $portalCategoryModel->adminCategoryTableTree($selectedIds, $tpl);
+
+        $categories = $portalCategoryModel->where('delete_time', 0)->select();
+
+        $this->assign('categories', $categories);
+        $this->assign('selectedIds', $selectedIds);
+        $this->assign('categories_tree', $categoryTree);
+        return $this->fetch();
+    }
+
+    /**
+     * 文章分类排序
+     * @adminMenu(
+     *     'name'   => '文章分类排序',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类排序',
+     *     'param'  => ''
+     * )
+     */
+    public function listOrder()
+    {
+        parent::listOrders(Db::name('portal_category'));
+        $this->success("排序更新成功!", '');
+    }
+
+    /**
+     * 文章分类显示隐藏
+     * @adminMenu(
+     *     'name'   => '文章分类显示隐藏',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类显示隐藏',
+     *     'param'  => ''
+     * )
+     */
+    public function toggle()
+    {
+        $data                = $this->request->param();
+        $portalCategoryModel = new PortalCategoryModel();
+        $ids                 = $this->request->param('ids/a');
+
+        if (isset($data['ids']) && !empty($data["display"])) {
+            $portalCategoryModel->where('id', 'in', $ids)->update(['status' => 1]);
+            $this->success("更新成功!");
+        }
+
+        if (isset($data['ids']) && !empty($data["hide"])) {
+            $portalCategoryModel->where('id', 'in', $ids)->update(['status' => 0]);
+            $this->success("更新成功!");
+        }
+
+    }
+
+    /**
+     * 删除文章分类
+     * @adminMenu(
+     *     'name'   => '删除文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除文章分类',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+        $id                  = $this->request->param('id');
+        //获取删除的内容
+        $findCategory = $portalCategoryModel->where('id', $id)->find();
+
+        if (empty($findCategory)) {
+            $this->error('分类不存在!');
+        }
+        //判断此分类有无子分类(不算被删除的子分类)
+        $categoryChildrenCount = $portalCategoryModel->where(['parent_id' => $id, 'delete_time' => 0])->count();
+
+        if ($categoryChildrenCount > 0) {
+            $this->error('此分类有子类无法删除!');
+        }
+
+        $categoryPostCount = Db::name('portal_category_post')->where('category_id', $id)->count();
+
+        if ($categoryPostCount > 0) {
+            $this->error('此分类有文章无法删除!');
+        }
+
+        $data   = [
+            'object_id'   => $findCategory['id'],
+            'create_time' => time(),
+            'table_name'  => 'portal_category',
+            'name'        => $findCategory['name']
+        ];
+        $result = $portalCategoryModel
+            ->where('id', $id)
+            ->update(['delete_time' => time()]);
+        if ($result) {
+            Db::name('recycleBin')->insert($data);
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+}

+ 32 - 0
app/portal/controller/AdminIndexController.php

@@ -0,0 +1,32 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\AdminBaseController;
+
+/**
+ * Class AdminIndexController
+ * @package app\portal\controller
+ * @adminMenuRoot(
+ *     'name'   =>'门户管理',
+ *     'action' =>'default',
+ *     'parent' =>'',
+ *     'display'=> true,
+ *     'order'  => 30,
+ *     'icon'   =>'th',
+ *     'remark' =>'门户管理'
+ * )
+ */
+class AdminIndexController extends AdminBaseController
+{
+
+
+}

+ 239 - 0
app/portal/controller/AdminPageController.php

@@ -0,0 +1,239 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\admin\model\RouteModel;
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalPostModel;
+use app\portal\service\PostService;
+use app\admin\model\ThemeModel;
+
+class AdminPageController extends AdminBaseController
+{
+
+    /**
+     * 页面管理
+     * @adminMenu(
+     *     'name'   => '页面管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '页面管理',
+     *     'param'  => ''
+     * )
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_page_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $param = $this->request->param();
+
+        $postService = new PostService();
+        $data        = $postService->adminPageList($param);
+        $data->appends($param);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('pages', $data->items());
+        $this->assign('page', $data->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加页面
+     * @adminMenu(
+     *     'name'   => '添加页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加页面',
+     *     'param'  => ''
+     * )
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_page_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $themeModel     = new ThemeModel();
+        $pageThemeFiles = $themeModel->getActionThemeFiles('portal/Page/index');
+        $this->assign('page_theme_files', $pageThemeFiles);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加页面提交
+     * @adminMenu(
+     *     'name'   => '添加页面提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加页面提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data['post'], 'AdminPage');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+            $data['post']['more']['photos'] = [];
+            foreach ($data['photo_urls'] as $key => $url) {
+                $photoUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+            }
+        }
+
+        if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+            $data['post']['more']['files'] = [];
+            foreach ($data['file_urls'] as $key => $url) {
+                $fileUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+            }
+        }
+
+        $portalPostModel = new PortalPostModel();
+        $portalPostModel->adminAddPage($data['post']);
+        $this->success(lang('ADD_SUCCESS'), url('AdminPage/edit', ['id' => $portalPostModel->id]));
+
+    }
+
+    /**
+     * 编辑页面
+     * @adminMenu(
+     *     'name'   => '编辑页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑页面',
+     *     'param'  => ''
+     * )
+     */
+    public function edit()
+    {
+        $content = hook_one('portal_admin_page_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+
+        $portalPostModel = new PortalPostModel();
+        $post            = $portalPostModel->where('id', $id)->find();
+
+        $themeModel     = new ThemeModel();
+        $pageThemeFiles = $themeModel->getActionThemeFiles('portal/Page/index');
+
+        $routeModel         = new RouteModel();
+        $alias              = $routeModel->getUrl('portal/Page/index', ['id' => $id]);
+        $post['post_alias'] = $alias;
+        $this->assign('page_theme_files', $pageThemeFiles);
+        $this->assign('post', $post);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑页面提交
+     * @adminMenu(
+     *     'name'   => '编辑页面提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑页面提交',
+     *     'param'  => ''
+     * )
+     */
+    public function editPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data['post'], 'AdminPage');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+            $data['post']['more']['photos'] = [];
+            foreach ($data['photo_urls'] as $key => $url) {
+                $photoUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+            }
+        }
+
+        if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+            $data['post']['more']['files'] = [];
+            foreach ($data['file_urls'] as $key => $url) {
+                $fileUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+            }
+        }
+
+        $portalPostModel = new PortalPostModel();
+
+        $portalPostModel->adminEditPage($data['post']);
+
+        $this->success(lang('SAVE_SUCCESS'));
+
+    }
+
+    /**
+     * 删除页面
+     * @author    iyting@foxmail.com
+     * @adminMenu(
+     *     'name'   => '删除页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除页面',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $portalPostModel = new PortalPostModel();
+        $data            = $this->request->param();
+
+        $result = $portalPostModel->adminDeletePage($data);
+        if ($result) {
+            $this->success(lang('DELETE_SUCCESS'));
+        } else {
+            $this->error(lang('DELETE_FAILED'));
+        }
+
+    }
+
+}

+ 153 - 0
app/portal/controller/AdminTagController.php

@@ -0,0 +1,153 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author:kane < chengjin005@163.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\portal\model\PortalTagModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+/**
+ * Class AdminTagController 标签管理控制器
+ * @package app\portal\controller
+ */
+class AdminTagController extends AdminBaseController
+{
+    /**
+     * 文章标签管理
+     * @adminMenu(
+     *     'name'   => '文章标签',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_tag_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $portalTagModel = new PortalTagModel();
+        $tags           = $portalTagModel->paginate();
+
+        $this->assign("arrStatus", $portalTagModel::$STATUS);
+        $this->assign("tags", $tags);
+        $this->assign('page', $tags->render());
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章标签
+     * @adminMenu(
+     *     'name'   => '添加文章标签',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function add()
+    {
+        $portalTagModel = new PortalTagModel();
+        $this->assign("arrStatus", $portalTagModel::$STATUS);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章标签提交
+     * @adminMenu(
+     *     'name'   => '添加文章标签提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章标签提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+
+        $arrData = $this->request->param();
+
+        $portalTagModel = new PortalTagModel();
+        $portalTagModel->isUpdate(false)->allowField(true)->save($arrData);
+
+        $this->success(lang("SAVE_SUCCESS"));
+
+    }
+
+    /**
+     * 更新文章标签状态
+     * @adminMenu(
+     *     'name'   => '更新标签状态',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '更新标签状态',
+     *     'param'  => ''
+     * )
+     */
+    public function upStatus()
+    {
+        $intId     = $this->request->param("id");
+        $intStatus = $this->request->param("status");
+        $intStatus = $intStatus ? 1 : 0;
+        if (empty($intId)) {
+            $this->error(lang("NO_ID"));
+        }
+
+        $portalTagModel = new PortalTagModel();
+        $portalTagModel->isUpdate(true)->save(["status" => $intStatus], ["id" => $intId]);
+
+        $this->success(lang("SAVE_SUCCESS"));
+
+    }
+
+    /**
+     * 删除文章标签
+     * @adminMenu(
+     *     'name'   => '删除文章标签',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $intId = $this->request->param("id", 0, 'intval');
+
+        if (empty($intId)) {
+            $this->error(lang("NO_ID"));
+        }
+        $portalTagModel = new PortalTagModel();
+
+        $portalTagModel->where('id' , $intId)->delete();
+        Db::name('portal_tag_post')->where('tag_id', $intId)->delete();
+        $this->success(lang("DELETE_SUCCESS"));
+    }
+}

+ 113 - 0
app/portal/controller/ArticleController.php

@@ -0,0 +1,113 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalCategoryModel;
+use app\portal\service\PostService;
+use app\portal\model\PortalPostModel;
+use think\Db;
+
+class ArticleController extends HomeBaseController
+{
+    /**
+     * 文章详情
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->getTopNav();
+        $portalCategoryModel = new PortalCategoryModel();
+        $postService         = new PostService();
+
+        $articleId  = $this->request->param('id', 0, 'intval');
+        $categoryId = $this->request->param('cid', 0, 'intval');
+        $article    = $postService->publishedArticle($articleId, $categoryId);
+
+        if (empty($article)) {
+            abort(404, '文章不存在!');
+        }
+
+        $tplName = 'article';
+        if (empty($categoryId)) {
+            $categories = $article['categories'];
+            if (count($categories) > 0) {
+                $this->assign('category', $categories[0]);
+            } else {
+                abort(404, '文章未指定分类!');
+            }
+        } else {
+            $category = $portalCategoryModel->where('id', $categoryId)->where('status', 1)->find();
+
+            if (empty($category)) {
+                abort(404, '文章不存在!');
+            }
+
+            $this->assign('category', $category);
+
+            $tplName = empty($category["one_tpl"]) ? $tplName : $category["one_tpl"];
+        }
+
+        Db::name('portal_post')->where('id', $articleId)->setInc('post_hits');
+        hook('portal_before_assign_article', $article);
+        $this->assign('article', $article);
+        $tplName = empty($article['more']['template']) ? $tplName : $article['more']['template'];
+
+        //获取分类
+        $id                  = $article['categories'][0]['id'];
+        $portalCategoryModel = new PortalCategoryModel();
+        $category            = $portalCategoryModel->where(function ($query) use ($id) {
+            $query->where('id', $id)->whereOr('parent_id', $id);
+        })->where('status', 1)->select();
+        $category_self       = [];
+        $category_child      = [];
+        foreach ($category as $v) {
+            if ($v['id'] == $id) {
+                $category_self = $v;
+            } else {
+                $category_child[] = $v;
+            }
+        }
+        $this->assign('category_self', $category_self);
+        $this->assign('category_child', $category_child);
+
+        //导航
+        $path = explode('-', $category_self['path']);
+        array_shift($path);
+        $path_list = $portalCategoryModel->where('id', 'in', $path)->select();
+        $this->assign('path_list', $path_list);
+
+        return $this->fetch("/" . $tplName);
+    }
+
+    // 文章点赞
+    public function doLike()
+    {
+        $this->checkUserLogin();
+        $articleId = $this->request->param('id', 0, 'intval');
+
+
+        $canLike = cmf_check_user_action("posts$articleId", 1);
+
+        if ($canLike) {
+            Db::name('portal_post')->where('id', $articleId)->setInc('post_like');
+
+            $this->success("赞好啦!");
+        } else {
+            $this->error("您已赞过啦!");
+        }
+    }
+
+}

+ 36 - 0
app/portal/controller/IndexController.php

@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\admin\model\SlideItemModel;
+use app\portal\model\PortalPostModel;
+use cmf\controller\HomeBaseController;
+
+class IndexController extends HomeBaseController
+{
+    public function index()
+    {
+        $this->getTopNav();
+        $data = [];
+
+        //轮播图
+        $data['slide'] = SlideItemModel::where('slide_id',1)->where('status',1)->order('list_order','asc')->select();
+
+        //文章
+        $portalModel = new PortalPostModel();
+        $data['xuexi']       = $portalModel->getListByCategory(1, 5);
+        $data['zhengce']     = $portalModel->getListByCategory(17, 5);
+        $data['xiazai']      = $portalModel->getListByCategory(2, 5);
+        $data['changjian']   = $portalModel->getListByCategory(3, 5);
+
+        return $this->fetch(':index', $data);
+    }
+}

+ 70 - 0
app/portal/controller/ListController.php

@@ -0,0 +1,70 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\portal\model\PortalPostModel;
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalCategoryModel;
+
+class ListController extends HomeBaseController
+{
+    /***
+     * 文章列表
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $this->getTopNav();
+
+        //获取分类
+        $id                  = $this->request->param('category_id', 0, 'intval');
+        $portalCategoryModel = new PortalCategoryModel();
+        $category            = $portalCategoryModel->where(function ($query) use ($id) {
+            $query->where('id', $id)->whereOr('parent_id', $id);
+        })->where('status', 1)->select();
+        $category_self       = [];
+        $category_child      = [];
+        foreach ($category as $v) {
+            if ($v['id'] == $id) {
+                $category_self = $v;
+            } else {
+                $category_child[] = $v;
+            }
+        }
+        $this->assign('category_self', $category_self);
+        $this->assign('category_child', $category_child);
+
+        //导航
+        $path = explode('-', $category_self['path']);
+        array_shift($path);
+        $path_list = $portalCategoryModel->where('id', 'in', $path)->select();
+        $this->assign('path_list', $path_list);
+
+        //文章列表
+        $keyword   = $this->request->param('keyword', '');
+        $condition = [];
+        if (!empty($keyword)) {
+            $condition[] = ['post_title', 'like', "%{$keyword}%"];
+        }
+        $portalModel = new PortalPostModel();
+        $list        = $portalModel->getListByCategory($id, 10, $condition);
+        $this->assign('keyword',$keyword);
+        $this->assign('list', $list);
+        $this->assign('page', $list->render());
+
+        $listTpl = empty($category_self['list_tpl']) ? 'list' : $category_self['list_tpl'];
+        return $this->fetch('/' . $listTpl);
+    }
+
+}

+ 44 - 0
app/portal/controller/PageController.php

@@ -0,0 +1,44 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\service\PostService;
+
+class PageController extends HomeBaseController
+{
+    /**
+     * 页面管理
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $postService = new PostService();
+        $pageId      = $this->request->param('id', 0, 'intval');
+        $page        = $postService->publishedPage($pageId);
+
+        if (empty($page)) {
+            abort(404, ' 页面不存在!');
+        }
+
+        $this->assign('page', $page);
+
+        $more = $page['more'];
+
+        $tplName = empty($more['template']) ? 'page' : $more['template'];
+
+        return $this->fetch("/$tplName");
+    }
+
+}

+ 32 - 0
app/portal/controller/SearchController.php

@@ -0,0 +1,32 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+
+class SearchController extends HomeBaseController
+{
+    /**
+     * 搜索
+     * @return mixed
+     */
+    public function index()
+    {
+        $keyword = $this->request->param('keyword');
+
+        if (empty($keyword)) {
+            $this -> error("关键词不能为空!请重新输入!");
+        }
+
+        $this -> assign("keyword", $keyword);
+        return $this->fetch('/search');
+    }
+}

+ 47 - 0
app/portal/controller/TagController.php

@@ -0,0 +1,47 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalTagModel;
+
+class TagController extends HomeBaseController
+{
+    /**
+     * 标签
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $id             = $this->request->param('id');
+
+        $portalTagModel = new PortalTagModel();
+
+        if(is_numeric($id)){
+            $tag = $portalTagModel->where('id', $id)->where('status', 1)->find();
+        }else{
+            $tag = $portalTagModel->where('name', $id)->where('status', 1)->find();
+        }
+
+
+        if (empty($tag)) {
+            abort(404, '标签不存在!');
+        }
+
+        $this->assign('tag', $tag);
+
+        return $this->fetch('/tag');
+    }
+
+}

+ 113 - 0
app/portal/data/portal.sql

@@ -0,0 +1,113 @@
+
+--
+-- 表的结构 `cmf_portal_category`
+--
+
+CREATE TABLE IF NOT EXISTS `cmf_portal_category` (
+  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类id',
+  `parent_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '分类父id',
+  `post_count` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '分类文章数',
+  `status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态,1:发布,0:不发布',
+  `delete_time` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
+  `list_order` float NOT NULL DEFAULT '10000' COMMENT '排序',
+  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '分类名称',
+  `description` varchar(255) NOT NULL DEFAULT '' COMMENT '分类描述',
+  `path` varchar(255) NOT NULL DEFAULT '' COMMENT '分类层级关系路径',
+  `seo_title` varchar(100) NOT NULL DEFAULT '',
+  `seo_keywords` varchar(255) NOT NULL DEFAULT '',
+  `seo_description` varchar(255) NOT NULL DEFAULT '',
+  `list_tpl` varchar(50) NOT NULL DEFAULT '' COMMENT '分类列表模板',
+  `one_tpl` varchar(50) NOT NULL DEFAULT '' COMMENT '分类文章页模板',
+  `more` text COMMENT '扩展属性',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='portal应用 文章分类表';
+
+-- --------------------------------------------------------
+
+--
+-- 表的结构 `cmf_portal_category_post`
+--
+
+CREATE TABLE IF NOT EXISTS `cmf_portal_category_post` (
+  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `post_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '文章id',
+  `category_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '分类id',
+  `list_order` float NOT NULL DEFAULT '10000' COMMENT '排序',
+  `status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态,1:发布;0:不发布',
+  PRIMARY KEY (`id`),
+  KEY `term_taxonomy_id` (`category_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='portal应用 分类文章对应表';
+
+-- --------------------------------------------------------
+
+--
+-- 表的结构 `cmf_portal_post`
+--
+
+CREATE TABLE IF NOT EXISTS `cmf_portal_post` (
+  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '父级id',
+  `post_type` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '类型,1:文章;2:页面',
+  `post_format` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '内容格式;1:html;2:md',
+  `user_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '发表者用户id',
+  `post_status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态;1:已发布;0:未发布;',
+  `comment_status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '评论状态;1:允许;0:不允许',
+  `is_top` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶;1:置顶;0:不置顶',
+  `recommended` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否推荐;1:推荐;0:不推荐',
+  `post_hits` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '查看数',
+  `post_like` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '点赞数',
+  `comment_count` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '评论数',
+  `create_time` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
+  `update_time` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '更新时间',
+  `published_time` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '发布时间',
+  `delete_time` int(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
+  `post_title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'post标题',
+  `post_keywords` varchar(150) NOT NULL DEFAULT '' COMMENT 'seo keywords',
+  `post_excerpt` varchar(500) NOT NULL DEFAULT '' COMMENT 'post摘要',
+  `post_source` varchar(150) NOT NULL DEFAULT '' COMMENT '转载文章的来源',
+  `post_content` text COMMENT '文章内容',
+  `post_content_filtered` text COMMENT '处理过的文章内容',
+  `more` text COMMENT '扩展属性,如缩略图;格式为json',
+  PRIMARY KEY (`id`),
+  KEY `type_status_date` (`post_type`,`post_status`,`create_time`,`id`),
+  KEY `parent_id` (`parent_id`),
+  KEY `user_id` (`user_id`),
+  KEY `create_time` (`create_time`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='portal应用 文章表' ROW_FORMAT=COMPACT;
+
+-- --------------------------------------------------------
+
+--
+-- 表的结构 `cmf_portal_tag`
+--
+
+CREATE TABLE IF NOT EXISTS `cmf_portal_tag` (
+  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '分类id',
+  `status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态,1:发布,0:不发布',
+  `recommended` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否推荐;1:推荐;0:不推荐',
+  `post_count` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '标签文章数',
+  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '标签名称',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='portal应用 文章标签表';
+
+-- --------------------------------------------------------
+
+--
+-- 表的结构 `cmf_portal_tag_post`
+--
+
+CREATE TABLE IF NOT EXISTS `cmf_portal_tag_post` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `tag_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '标签 id',
+  `post_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '文章 id',
+  `status` tinyint(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态,1:发布;0:不发布',
+  PRIMARY KEY (`id`),
+  KEY `post_id` (`post_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='portal应用 标签文章对应表';
+
+-- --------------------------------------------------------
+
+
+-- 增缩略图字段
+ALTER TABLE `cmf_portal_post` ADD `thumbnail` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '缩略图' AFTER `post_source`;
+ALTER TABLE `cmf_portal_post` ADD `post_favorites` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '收藏数' AFTER `post_hits`;

+ 96 - 0
app/portal/hooks.php

@@ -0,0 +1,96 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'portal_before_assign_article'    => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '文章显示之前', // 钩子名称
+        "description" => "文章显示之前", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_after_save_article' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '后台文章保存之后', // 钩子名称
+        "description" => "后台文章保存之后", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_article_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章管理列表界面', // 钩子名称
+        "description" => "门户后台文章管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加界面', // 钩子名称
+        "description" => "门户后台文章添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章编辑界面', // 钩子名称
+        "description" => "门户后台文章编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类管理列表界面', // 钩子名称
+        "description" => "门户后台文章分类管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类添加界面', // 钩子名称
+        "description" => "门户后台文章分类添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类编辑界面', // 钩子名称
+        "description" => "门户后台文章分类编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面管理列表界面', // 钩子名称
+        "description" => "门户后台页面管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面添加界面', // 钩子名称
+        "description" => "门户后台页面添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面编辑界面', // 钩子名称
+        "description" => "门户后台页面编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_tag_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章标签管理列表界面', // 钩子名称
+        "description" => "门户后台文章标签管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view_right_sidebar' => [
+        "type"        => 4,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加编辑界面右侧栏', // 钩子名称
+        "description" => "门户后台文章添加编辑界面右侧栏", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view_main' => [
+        "type"        => 4,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加编辑界面主要内容', // 钩子名称
+        "description" => "门户后台文章添加编辑界面主要内容", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+];

+ 13 - 0
app/portal/lang/en-us.php

@@ -0,0 +1,13 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'ADMIN_CENTER' => 'Admin Center',
+];

+ 15 - 0
app/portal/lang/en-us/common.php

@@ -0,0 +1,15 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'TABLE_PORTAL_CATEGORY'  => 'Article Category',
+    'TABLE_PORTAL_POST'      => 'Article',
+    'TABLE_PORTAL_POST#PAGE' => 'Page'
+];

+ 21 - 0
app/portal/lang/zh-cn.php

@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'ADMIN_TAG_ADD'    => '添加标签',
+    'ADMIN_TAG_DELETE' => '删除标签',
+    'ADMIN_TAG_INDEX'  => '标签列表',
+    'SAVE_SUCCESS'     => '保存成功!',
+    'DELETE_SUCCESS'   => '删除成功!',
+    'ADD_SUCCESS'      => '添加成功',
+    'DELETE_FAILED'    => '删除失败',
+    'ADD_FAILED'       => '添加失败',
+];
+

+ 16 - 0
app/portal/lang/zh-cn/common.php

@@ -0,0 +1,16 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'TABLE_PORTAL_CATEGORY'  => '文章分类',
+    'TABLE_PORTAL_POST'      => '文章',
+    'TABLE_SLIDE'      => '幻灯片',
+    'TABLE_PORTAL_POST#PAGE' => '页面'
+];

+ 13 - 0
app/portal/lang/zh-cn/home.php

@@ -0,0 +1,13 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+/*前台通用语言包*/
+return [
+];

+ 217 - 0
app/portal/model/PortalCategoryModel.php

@@ -0,0 +1,217 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use app\admin\model\RouteModel;
+use think\db\Query;
+use think\Model;
+use tree\Tree;
+
+class PortalCategoryModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    /**
+     * 生成分类 select树形结构
+     * @param int $selectId   需要选中的分类 id
+     * @param int $currentCid 需要隐藏的分类 id
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminCategoryTree($selectId = 0, $currentCid = 0)
+    {
+        $categories = $this->order("list_order ASC")
+            ->where('delete_time', 0)
+            ->where(function (Query $query) use ($currentCid) {
+                if (!empty($currentCid)) {
+                    $query->where('id', 'neq', $currentCid);
+                }
+            })
+            ->select()->toArray();
+
+        $tree       = new Tree();
+        $tree->icon = ['&nbsp;&nbsp;│', '&nbsp;&nbsp;├─', '&nbsp;&nbsp;└─'];
+        $tree->nbsp = '&nbsp;&nbsp;';
+
+        $newCategories = [];
+        foreach ($categories as $item) {
+            $item['selected'] = $selectId == $item['id'] ? "selected" : "";
+
+            array_push($newCategories, $item);
+        }
+
+        $tree->init($newCategories);
+        $str     = '<option value=\"{$id}\" {$selected}>{$spacer}{$name}</option>';
+        $treeStr = $tree->getTree(0, $str);
+
+        return $treeStr;
+    }
+
+    /**
+     * 分类树形结构
+     * @param int    $currentIds
+     * @param string $tpl
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminCategoryTableTree($currentIds = 0, $tpl = '')
+    {
+//        if (!empty($currentCid)) {
+//            $where['id'] = ['neq', $currentCid];
+//        }
+        $categories = $this->order("list_order ASC")->where('delete_time', 0)->select()->toArray();
+
+        $tree       = new Tree();
+        $tree->icon = ['&nbsp;&nbsp;│', '&nbsp;&nbsp;├─', '&nbsp;&nbsp;└─'];
+        $tree->nbsp = '&nbsp;&nbsp;';
+
+        if (!is_array($currentIds)) {
+            $currentIds = [$currentIds];
+        }
+
+        $newCategories = [];
+        foreach ($categories as $item) {
+            $item['parent_id_node'] = ($item['parent_id']) ? ' class="child-of-node-' . $item['parent_id'] . '"' : '';
+            $item['style']          = empty($item['parent_id']) ? '' : 'display:none;';
+            $item['status_text']    = empty($item['status']) ? '<span class="label label-warning">隐藏</span>' : '<span class="label label-success">显示</span>';
+            $item['checked']        = in_array($item['id'], $currentIds) ? "checked" : "";
+            $item['url']            = cmf_url('portal/List/index', ['id' => $item['id']]);
+            $item['str_action']     = '<a class="btn btn-xs btn-primary" href="' . url("AdminCategory/add", ["parent" => $item['id']]) . '">添加子分类</a>  <a class="btn btn-xs btn-primary" href="' . url("AdminCategory/edit", ["id" => $item['id']]) . '">' . lang('EDIT') . '</a>  <a class="btn btn-xs btn-danger js-ajax-delete" href="' . url("AdminCategory/delete", ["id" => $item['id']]) . '">' . lang('DELETE') . '</a> ';
+            if ($item['status']) {
+                $item['str_action'] .= '<a class="btn btn-xs btn-warning js-ajax-dialog-btn" data-msg="您确定隐藏此分类吗" href="' . url('AdminCategory/toggle', ['ids' => $item['id'], 'hide' => 1]) . '">隐藏</a>';
+            } else {
+                $item['str_action'] .= '<a class="btn btn-xs btn-success js-ajax-dialog-btn" data-msg="您确定显示此分类吗" href="' . url('AdminCategory/toggle', ['ids' => $item['id'], 'display' => 1]) . '">显示</a>';
+            }
+            array_push($newCategories, $item);
+        }
+
+        $tree->init($newCategories);
+
+        if (empty($tpl)) {
+            $tpl = " <tr id='node-\$id' \$parent_id_node style='\$style' data-parent_id='\$parent_id' data-id='\$id'>
+                        <td style='padding-left:20px;'><input type='checkbox' class='js-check' data-yid='js-check-y' data-xid='js-check-x' name='ids[]' value='\$id' data-parent_id='\$parent_id' data-id='\$id'></td>
+                        <td><input name='list_orders[\$id]' type='text' size='3' value='\$list_order' class='input-order'></td>
+                        <td>\$id</td>
+                        <td>\$spacer <a href='\$url' target='_blank'>\$name</a></td>
+                        <td>\$description</td>
+                        <td>\$status_text</td>
+                        <td>\$str_action</td>
+                    </tr>";
+        }
+        $treeStr = $tree->getTree(0, $tpl);
+
+        return $treeStr;
+    }
+
+    /**
+     * 添加文章分类
+     * @param $data
+     * @return bool
+     */
+    public function addCategory($data)
+    {
+        $result = true;
+        self::startTrans();
+        try {
+            if (!empty($data['more']['thumbnail'])) {
+                $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            }
+            $this->allowField(true)->save($data);
+            $id = $this->id;
+            if (empty($data['parent_id'])) {
+
+                $this->where('id', $id)->update(['path' => '0-' . $id]);
+            } else {
+                $parentPath = $this->where('id', intval($data['parent_id']))->value('path');
+                $this->where('id', $id)->update(['path' => "$parentPath-$id"]);
+
+            }
+            self::commit();
+        } catch (\Exception $e) {
+            self::rollback();
+            $result = false;
+        }
+
+        if ($result != false) {
+            //设置别名
+            $routeModel = new RouteModel();
+            if (!empty($data['alias']) && !empty($id)) {
+                $routeModel->setRoute($data['alias'], 'portal/List/index', ['id' => $id], 2, 5000);
+                $routeModel->setRoute($data['alias'] . '/:id', 'portal/Article/index', ['cid' => $id], 2, 4999);
+            }
+            $routeModel->getRoutes(true);
+        }
+
+        return $result;
+    }
+
+    public function editCategory($data)
+    {
+        $result = true;
+
+        $id          = intval($data['id']);
+        $parentId    = intval($data['parent_id']);
+        $oldCategory = $this->where('id', $id)->find();
+
+        if (empty($parentId)) {
+            $newPath = '0-' . $id;
+        } else {
+            $parentPath = $this->where('id', intval($data['parent_id']))->value('path');
+            if ($parentPath === false) {
+                $newPath = false;
+            } else {
+                $newPath = "$parentPath-$id";
+            }
+        }
+
+        if (empty($oldCategory) || empty($newPath)) {
+            $result = false;
+        } else {
+
+            $data['path'] = $newPath;
+            if (!empty($data['more']['thumbnail'])) {
+                $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            }
+            $this->isUpdate(true)->allowField(true)->save($data, ['id' => $id]);
+
+            $children = $this->field('id,path')->where('path', 'like', $oldCategory['path'] . "-%")->select();
+            if (!$children->isEmpty()) {
+                foreach ($children as $child) {
+                    $childPath = str_replace($oldCategory['path'] . '-', $newPath . '-', $child['path']);
+                    $this->where('id', $child['id'])->update(['path' => $childPath], ['id' => $child['id']]);
+                }
+            }
+
+            $routeModel = new RouteModel();
+            if (!empty($data['alias'])) {
+                $routeModel->setRoute($data['alias'], 'portal/List/index', ['id' => $data['id']], 2, 5000);
+                $routeModel->setRoute($data['alias'] . '/:id', 'portal/Article/index', ['cid' => $data['id']], 2, 4999);
+            } else {
+                $routeModel->deleteRoute('portal/List/index', ['id' => $data['id']]);
+                $routeModel->deleteRoute('portal/Article/index', ['cid' => $data['id']]);
+            }
+
+            $routeModel->getRoutes(true);
+        }
+
+
+        return $result;
+    }
+
+
+}

+ 416 - 0
app/portal/model/PortalPostModel.php

@@ -0,0 +1,416 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use app\admin\model\RouteModel;
+use think\Model;
+use think\Db;
+
+/**
+ * @property mixed id
+ */
+class PortalPostModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = true;
+
+    /**
+     * 关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function user()
+    {
+        return $this->belongsTo('UserModel', 'user_id')->setEagerlyType(1);
+    }
+
+    /**
+     * 关联分类表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function categories()
+    {
+        return $this->belongsToMany('PortalCategoryModel', 'portal_category_post', 'category_id', 'post_id');
+    }
+
+    /**
+     * 关联标签表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function tags()
+    {
+        return $this->belongsToMany('PortalTagModel', 'portal_tag_post', 'tag_id', 'post_id');
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPostContentAttr($value)
+    {
+        return cmf_replace_content_file_url(htmlspecialchars_decode($value));
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function setPostContentAttr($value)
+    {
+        return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
+    }
+
+    /**
+     * published_time 自动完成
+     * @param $value
+     * @return false|int
+     */
+    public function setPublishedTimeAttr($value)
+    {
+        return strtotime($value);
+    }
+
+    /**
+     * 后台管理添加文章
+     * @param array $data 文章数据
+     * @param array|string $categories 文章分类 id
+     * @return $this
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function adminAddArticle($data, $categories)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            $data['thumbnail']         = $data['more']['thumbnail'];
+        }
+
+        if (!empty($data['more']['audio'])) {
+            $data['more']['audio'] = cmf_asset_relative_url($data['more']['audio']);
+        }
+
+        if (!empty($data['more']['video'])) {
+            $data['more']['video'] = cmf_asset_relative_url($data['more']['video']);
+        }
+
+        $this->allowField(true)->data($data, true)->isUpdate(false)->save();
+
+        if (is_string($categories)) {
+            $categories = explode(',', $categories);
+        }
+
+        $this->categories()->save($categories);
+
+        $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+
+        $keywords = explode(',', $data['post_keywords']);
+
+        $this->addTags($keywords, $this->id);
+
+        return $this;
+
+    }
+
+    /**
+     * 后台管理编辑文章
+     * @param array $data 文章数据
+     * @param array|string $categories 文章分类 id
+     * @return $this
+     * @throws \think\Exception
+     */
+    public function adminEditArticle($data, $categories)
+    {
+
+        unset($data['user_id']);
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            $data['thumbnail']         = $data['more']['thumbnail'];
+        }
+
+        if (!empty($data['more']['audio'])) {
+            $data['more']['audio'] = cmf_asset_relative_url($data['more']['audio']);
+        }
+
+        if (!empty($data['more']['video'])) {
+            $data['more']['video'] = cmf_asset_relative_url($data['more']['video']);
+        }
+
+        $this->allowField(true)->isUpdate(true)->data($data, true)->save();
+
+        if (is_string($categories)) {
+            $categories = explode(',', $categories);
+        }
+
+        $oldCategoryIds        = $this->categories()->column('category_id');
+        $sameCategoryIds       = array_intersect($categories, $oldCategoryIds);
+        $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
+        $newCategoryIds        = array_diff($categories, $sameCategoryIds);
+
+        if (!empty($needDeleteCategoryIds)) {
+            $this->categories()->detach($needDeleteCategoryIds);
+        }
+
+        if (!empty($newCategoryIds)) {
+            $this->categories()->attach(array_values($newCategoryIds));
+        }
+
+
+        $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+
+        $keywords = explode(',', $data['post_keywords']);
+
+        $this->addTags($keywords, $data['id']);
+
+        return $this;
+
+    }
+
+    /**
+     * 增加标签
+     * @param $keywords
+     * @param $articleId
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function addTags($keywords, $articleId)
+    {
+        $portalTagModel = new PortalTagModel();
+
+        $tagIds = [];
+
+        $data = [];
+
+        if (!empty($keywords)) {
+
+            $oldTagIds = Db::name('portal_tag_post')->where('post_id', $articleId)->column('tag_id');
+
+            foreach ($keywords as $keyword) {
+                $keyword = trim($keyword);
+                if (!empty($keyword)) {
+                    $findTag = $portalTagModel->where('name', $keyword)->find();
+                    if (empty($findTag)) {
+                        $tagId = $portalTagModel->insertGetId([
+                            'name' => $keyword,
+                        ]);
+                    } else {
+                        $tagId = $findTag['id'];
+                    }
+
+                    if (!in_array($tagId, $oldTagIds)) {
+                        array_push($data, ['tag_id' => $tagId, 'post_id' => $articleId]);
+                    }
+
+                    array_push($tagIds, $tagId);
+
+                }
+            }
+
+
+            if (empty($tagIds) && !empty($oldTagIds)) {
+                Db::name('portal_tag_post')->where('post_id', $articleId)->delete();
+            }
+
+            $sameTagIds = array_intersect($oldTagIds, $tagIds);
+
+            $shouldDeleteTagIds = array_diff($oldTagIds, $sameTagIds);
+
+            if (!empty($shouldDeleteTagIds)) {
+                Db::name('portal_tag_post')
+                    ->where('post_id', $articleId)
+                    ->where('tag_id', 'in', $shouldDeleteTagIds)
+                    ->delete();
+            }
+
+            if (!empty($data)) {
+                Db::name('portal_tag_post')->insertAll($data);
+            }
+
+
+        } else {
+            Db::name('portal_tag_post')->where('post_id', $articleId)->delete();
+        }
+    }
+
+    /**
+     * @param $data
+     * @return bool
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminDeletePage($data)
+    {
+
+        if (isset($data['id'])) {
+            $id = $data['id']; //获取删除id
+
+            $res = $this->where('id', $id)->find();
+
+            if ($res) {
+                $res = json_decode(json_encode($res), true); //转换为数组
+
+                $recycleData = [
+                    'object_id'   => $res['id'],
+                    'create_time' => time(),
+                    'table_name'  => 'portal_post#page',
+                    'name'        => $res['post_title'],
+
+                ];
+
+                Db::startTrans(); //开启事务
+                $transStatus = false;
+                try {
+                    Db::name('portal_post')->where('id', $id)->update([
+                        'delete_time' => time(),
+                    ]);
+                    Db::name('recycle_bin')->insert($recycleData);
+
+                    $transStatus = true;
+                    // 提交事务
+                    Db::commit();
+                } catch (\Exception $e) {
+
+                    // 回滚事务
+                    Db::rollback();
+                }
+                return $transStatus;
+
+
+            } else {
+                return false;
+            }
+        } elseif (isset($data['ids'])) {
+            $ids = $data['ids'];
+
+            $res = $this->where('id', 'in', $ids)
+                ->select();
+
+            if ($res) {
+                $res = json_decode(json_encode($res), true);
+                foreach ($res as $key => $value) {
+                    $recycleData[$key]['object_id']   = $value['id'];
+                    $recycleData[$key]['create_time'] = time();
+                    $recycleData[$key]['table_name']  = 'portal_post';
+                    $recycleData[$key]['name']        = $value['post_title'];
+
+                }
+
+                Db::startTrans(); //开启事务
+                $transStatus = false;
+                try {
+                    Db::name('portal_post')->where('id', 'in', $ids)
+                        ->update([
+                            'delete_time' => time(),
+                        ]);
+
+
+                    Db::name('recycle_bin')->insertAll($recycleData);
+
+                    $transStatus = true;
+                    // 提交事务
+                    Db::commit();
+
+                } catch (\Exception $e) {
+
+                    // 回滚事务
+                    Db::rollback();
+
+
+                }
+                return $transStatus;
+
+
+            } else {
+                return false;
+            }
+
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 后台管理添加页面
+     * @param array $data 页面数据
+     * @return $this
+     */
+    public function adminAddPage($data)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+        }
+
+        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+        $data['post_type']   = 2;
+        $this->allowField(true)->data($data, true)->save();
+
+        return $this;
+
+    }
+
+    /**
+     * 后台管理编辑页面
+     * @param array $data 页面数据
+     * @return $this
+     */
+    public function adminEditPage($data)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+        }
+
+        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+        $data['post_type']   = 2;
+        $this->allowField(true)->isUpdate(true)->data($data, true)->save();
+
+        $routeModel = new RouteModel();
+        $routeModel->setRoute($data['post_alias'], 'portal/Page/index', ['id' => $data['id']], 2, 5000);
+
+        $routeModel->getRoutes(true);
+        return $this;
+    }
+
+    /**
+     * 获取文章列表
+     */
+    public function getListByCategory($category_id, $pagesize = 10, $where = [])
+    {
+        $list = $this->alias('post')
+            ->field('post.*')
+            ->join('__PORTAL_CATEGORY_POST__ category_post', 'post.id=category_post.post_id')
+            ->where('category_post.category_id', $category_id)
+            ->where('post.post_status', 1)
+            ->where($where)
+            ->order('category_post.list_order', 'asc')
+            ->paginate($pagesize,false,['query'=>input('param.')]);
+
+        return $list;
+    }
+}

+ 21 - 0
app/portal/model/PortalTagModel.php

@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author:kane < chengjin005@163.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use think\Model;
+
+class PortalTagModel extends Model
+{
+    public static   $STATUS = array(
+        0=>"未启用",
+        1=>"已启用",
+    );
+}

+ 23 - 0
app/portal/model/UserModel.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use think\Model;
+
+class UserModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+
+}

+ 14 - 0
app/portal/nav.php

@@ -0,0 +1,14 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    ['name' => '文章分类', 'api' => "Category/nav"],
+    ['name' => '所有页面', 'api' => "Page/nav"]
+];

+ 459 - 0
app/portal/service/ApiService.php

@@ -0,0 +1,459 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\service;
+
+use app\portal\model\PortalPostModel;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use think\db\Query;
+
+class ApiService
+{
+    /**
+     * 功能:查询文章列表,支持分页;<br>
+     * 注:此方法查询时关联两个表portal_category_post(category_post),portal_post(post);在指定排序(order),指定查询条件(where)最好指定一下表别名
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'category_ids'=>'',
+     *                     'where'=>'',
+     *                     'limit'=>'',
+     *                     'order'=>'',
+     *                     'page'=>'',
+     *                     'relation'=>''
+     *                     )
+     *                     字段说明:
+     *                     category_ids:文章所在分类,可指定一个或多个分类id,以英文逗号分隔,如1或1,2,3 默认值为全部
+     *                     field:调用指定的字段@todo
+     *                     如只调用posts表里的id和post_title字段可以是post.id,post.post_title; 默认全部,
+     *                     此方法查询时关联两个表portal_category_post(category_post),portal_post(post);
+     *                     所以最好指定一下表名,以防字段冲突
+     *                     limit:数据条数,默认值为10,可以指定从第几条开始,如3,8(表示共调用8条,从第3条开始)
+     *                     order:排序方式,如按posts表里的published_time字段倒序排列:post.published_time desc
+     *                     where:查询条件,字符串形式,和sql语句一样,请在事先做好安全过滤,最好使用第二个参数$where的数组形式进行过滤,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突,查询条件(只支持数组),格式和thinkPHP的where方法一样,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突;
+     *                     </pre>
+     * @return array 包括分页的文章列表<pre>
+     *                     格式:
+     *                     array(
+     *                     "articles"=>array(),//文章列表,array
+     *                     "page"=>"",//生成的分页html,不分页则没有此项
+     *                     "total"=>100, //符合条件的文章总数,不分页则没有此项
+     *                     "total_pages"=>5 // 总页数,不分页则没有此项
+     *                     )</pre>
+     */
+    public static function articles($param)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post.post_status' => 1,
+            'post.post_type'   => 1,
+            'post.delete_time' => 0
+        ];
+
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $limit       = empty($param['limit']) ? 10 : $param['limit'];
+        $order       = empty($param['order']) ? '' : $param['order'];
+        $page        = isset($param['page']) ? $param['page'] : false;
+        $relation    = empty($param['relation']) ? '' : $param['relation'];
+        $categoryIds = empty($param['category_ids']) ? '' : $param['category_ids'];
+
+        $join = [
+            //['__USER__ user', 'post.user_id = user.id'],
+        ];
+
+        $whereCategoryId = null;
+
+        if (!empty($categoryIds)) {
+
+            $field = !empty($param['field']) ? $param['field'] : 'post.*,min(category_post.category_id) as category_id';
+            array_push($join, ['__PORTAL_CATEGORY_POST__ category_post', 'post.id = category_post.post_id']);
+
+            if (!is_array($categoryIds)) {
+                $categoryIds = explode(',', $categoryIds);
+            }
+
+            if (count($categoryIds) == 1) {
+                $whereCategoryId = function (Query $query) use ($categoryIds) {
+                    $query->where('category_post.category_id', $categoryIds[0]);
+                };
+            } else {
+                $whereCategoryId = function (Query $query) use ($categoryIds) {
+                    $query->where('category_post.category_id', 'in', $categoryIds);
+                };
+            }
+        } else {
+
+            $field = !empty($param['field']) ? $param['field'] : 'post.*,min(category_post.category_id) as category_id';
+            array_push($join, ['__PORTAL_CATEGORY_POST__ category_post', 'post.id = category_post.post_id']);
+        }
+
+        $articles = $portalPostModel->alias('post')->field($field)
+            ->join($join)
+            ->where($where)
+            ->where($paramWhere)
+            ->where($whereCategoryId)
+            ->where('post.published_time', ['> time', 0], ['<', time()], 'and')
+            ->order($order)
+            ->group('post.id');
+
+        $return = [];
+
+        if (empty($page)) {
+            $articles = $articles->limit($limit)->select();
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $return['articles'] = $articles;
+        } else {
+
+            if (is_array($page)) {
+                if (empty($page['list_rows'])) {
+                    $page['list_rows'] = 10;
+                }
+
+                $articles = $articles->paginate($page);
+            } else {
+                $articles = $articles->paginate(intval($page));
+            }
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $articles->appends(request()->get());
+            $articles->appends(request()->post());
+
+            $return['articles']    = $articles->items();
+            $return['page']        = $articles->render();
+            $return['total']       = $articles->total();
+            $return['total_pages'] = $articles->lastPage();
+        }
+
+
+        return $return;
+
+    }
+
+    /**
+     * 功能:查询标签文章列表,支持分页;<br>
+     * 注:此方法查询时关联两个表portal_tag_post(tag_post),portal_post(post);在指定排序(order),指定查询条件(where)最好指定一下表别名
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'tag_id'=>'',
+     *                     'where'=>'',
+     *                     'limit'=>'',
+     *                     'order'=>'',
+     *                     'page'=>'',
+     *                     'relation'=>''
+     *                     )
+     *                     字段说明:
+     *                     field:调用指定的字段@todo
+     *                     如只调用posts表里的id和post_title字段可以是post.id,post.post_title; 默认全部,
+     *                     此方法查询时关联两个表portal_tag_post(category_post),portal_post(post);
+     *                     所以最好指定一下表名,以防字段冲突
+     *                     limit:数据条数,默认值为10,可以指定从第几条开始,如3,8(表示共调用8条,从第3条开始)
+     *                     order:排序方式,如按posts表里的published_time字段倒序排列:post.published_time desc
+     *                     where:查询条件,字符串形式,和sql语句一样,请在事先做好安全过滤,最好使用第二个参数$where的数组形式进行过滤,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突,查询条件(只支持数组),格式和thinkPHP的where方法一样,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突;
+     *                     </pre>
+     * @return array 包括分页的文章列表<pre>
+     *                     格式:
+     *                     array(
+     *                     "articles"=>array(),//文章列表,array
+     *                     "page"=>"",//生成的分页html,不分页则没有此项
+     *                     "total"=>100, //符合条件的文章总数,不分页则没有此项
+     *                     "total_pages"=>5 // 总页数,不分页则没有此项
+     *                     )</pre>
+     */
+    public static function tagArticles($param)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post.post_status' => 1,
+            'post.post_type'   => 1,
+            'post.delete_time' => 0
+        ];
+
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $limit    = empty($param['limit']) ? 10 : $param['limit'];
+        $order    = empty($param['order']) ? '' : $param['order'];
+        $page     = isset($param['page']) ? $param['page'] : false;
+        $relation = empty($param['relation']) ? '' : $param['relation'];
+        $tagId    = empty($param['tag_id']) ? '' : $param['tag_id'];
+
+        $join = [
+            //['__USER__ user', 'post.user_id = user.id'],
+        ];
+
+        if (empty($tagId)) {
+            return null;
+
+        } else {
+            $field = !empty($param['field']) ? $param['field'] : 'post.*';
+            array_push($join, ['__PORTAL_TAG_POST__ tag_post', 'post.id = tag_post.post_id']);
+
+            $where['tag_post.tag_id'] = $tagId;
+        }
+
+        $articles = $portalPostModel->alias('post')->field($field)
+            ->join($join)
+            ->where($where)
+            ->where($paramWhere)
+            ->where('post.published_time', ['> time', 0], ['<', time()], 'and')
+            ->order($order);
+
+        $return = [];
+
+        if (empty($page)) {
+            $articles = $articles->limit($limit)->select();
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $return['articles'] = $articles;
+        } else {
+
+            if (is_array($page)) {
+                if (empty($page['list_rows'])) {
+                    $page['list_rows'] = 10;
+                }
+
+                $articles = $articles->paginate($page);
+            } else {
+                $articles = $articles->paginate(intval($page));
+            }
+
+            if (!empty($relation) && !empty($articles->items())) {
+                $articles->load($relation);
+            }
+
+            $articles->appends(request()->get());
+            $articles->appends(request()->post());
+
+            $return['articles']    = $articles->items();
+            $return['page']        = $articles->render();
+            $return['total']       = $articles->total();
+            $return['total_pages'] = $articles->lastPage();
+        }
+
+        return $return;
+    }
+
+    /**
+     * 获取指定id的文章
+     * @param int $id
+     * @return array|false|\PDOStatement|string|\think\Model
+     */
+    public static function article($id)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 1,
+            'id'          => $id,
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel->where($where)
+            ->where('published_time', ['> time', 0], ['<', time()], 'and')
+            ->find();
+    }
+
+    /**
+     * 获取指定条件的页面列表
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'where'=>'',
+     *                     'order'=>'',
+     *                     )</pre>
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public static function pages($param)
+    {
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $order = empty($param['order']) ? '' : $param['order'];
+
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 2, //页面
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel
+            ->where($where)
+            ->where($paramWhere)
+            ->where('published_time', [['> time', 0], ['<', time()]], 'and')
+            ->order($order)
+            ->select();
+    }
+
+    /**
+     * 获取指定id的页面
+     * @param int $id 页面的id
+     * @return array|false|\PDOStatement|string|\think\Model 返回符合条件的页面
+     */
+    public static function page($id)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 2,
+            'id'          => $id,
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel->where($where)
+            ->where('published_time', ['> time', 0], ['<', time()], 'and')
+            ->find();
+    }
+
+    /**
+     * 返回指定分类
+     * @param int $id 分类id
+     * @return array 返回符合条件的分类
+     */
+    public static function category($id)
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+            'id'          => $id
+        ];
+
+        return $portalCategoryModel->where($where)->find();
+    }
+
+    /**
+     * 返回指定分类下的子分类
+     * @param int $categoryId 分类id
+     * @param     $field      string  指定查询字段
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @return false|\PDOStatement|string|\think\Collection 返回指定分类下的子分类
+     */
+    public static function subCategories($categoryId, $field = '*')
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+            'parent_id'   => $categoryId
+        ];
+
+		return $portalCategoryModel->field($field)->where($where)->order('list_order ASC')->select();
+	}
+
+    /**
+     * 返回指定分类下的所有子分类
+     * @param int $categoryId 分类id
+     * @return array 返回指定分类下的所有子分类
+     */
+    public static function allSubCategories($categoryId)
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $categoryId = intval($categoryId);
+
+        if ($categoryId !== 0) {
+            $category = $portalCategoryModel->field('path')->where('id', $categoryId)->find();
+
+            if (empty($category)) {
+                return [];
+            }
+
+            $categoryPath = $category['path'];
+        } else {
+            $categoryPath = 0;
+        }
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0
+        ];
+
+        return $portalCategoryModel->where($where)->whereLike('path', "$categoryPath-%")->select();
+    }
+
+    /**
+     * 返回符合条件的所有分类
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'where'=>'',
+     *                     'order'=>'',
+     *                     )</pre>
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public static function categories($param)
+    {
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $order = empty($param['order']) ? '' : $param['order'];
+
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+        ];
+
+        $temp = $portalCategoryModel
+            ->where($where)
+            ->where($paramWhere)
+            ->order($order);
+
+        if (!empty($param['ids'])) {
+            $temp->whereIn('id', $param['ids']);
+        }
+        return $temp->select();
+    }
+
+    /**
+     * 获取面包屑数据
+     * @param int     $categoryId  当前文章所在分类,或者当前分类的id
+     * @param boolean $withCurrent 是否获取当前分类
+     * @return array 面包屑数据
+     */
+    public static function breadcrumb($categoryId, $withCurrent = false)
+    {
+        $data                = [];
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $path = $portalCategoryModel->where(['id' => $categoryId])->value('path');
+
+        if (!empty($path)) {
+            $parents = explode('-', $path);
+            if (!$withCurrent) {
+                array_pop($parents);
+            }
+
+            if (!empty($parents)) {
+                $data = $portalCategoryModel->where('id', 'in', $parents)->order('path ASC')->select();
+            }
+        }
+
+        return $data;
+    }
+
+}

+ 287 - 0
app/portal/service/PostService.php

@@ -0,0 +1,287 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\service;
+
+use app\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PostService
+{
+    /**
+     * 文章查询
+     * @param $filter
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminArticleList($filter)
+    {
+        return $this->adminPostList($filter);
+    }
+
+    /**
+     * 页面文章列表
+     * @param $filter
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminPageList($filter)
+    {
+        return $this->adminPostList($filter, true);
+    }
+
+    /**
+     * 文章查询
+     * @param      $filter
+     * @param bool $isPage
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminPostList($filter, $isPage = false)
+    {
+
+        $join = [
+            ['__USER__ u', 'a.user_id = u.id']
+        ];
+
+        $field = 'a.*,u.user_login,u.user_nickname,u.user_email';
+
+        $category = empty($filter['category']) ? 0 : intval($filter['category']);
+        if (!empty($category)) {
+            array_push($join, [
+                '__PORTAL_CATEGORY_POST__ b', 'a.id = b.post_id'
+            ]);
+            $field = 'a.*,b.id AS post_category_id,b.list_order,b.category_id,u.user_login,u.user_nickname,u.user_email';
+        }
+
+        $portalPostModel = new PortalPostModel();
+        $articles        = $portalPostModel->alias('a')->field($field)
+            ->join($join)
+            ->where('a.create_time', '>=', 0)
+            ->where('a.delete_time', 0)
+            ->where(function (Query $query) use ($filter, $isPage) {
+
+                $category = empty($filter['category']) ? 0 : intval($filter['category']);
+                if (!empty($category)) {
+                    $query->where('b.category_id', $category);
+                }
+
+                $startTime = empty($filter['start_time']) ? 0 : strtotime($filter['start_time']);
+                $endTime   = empty($filter['end_time']) ? 0 : strtotime($filter['end_time']);
+                if (!empty($startTime)) {
+                    $query->where('a.published_time', '>=', $startTime);
+                }
+                if (!empty($endTime)) {
+                    $query->where('a.published_time', '<=', $endTime);
+                }
+
+                $keyword = empty($filter['keyword']) ? '' : $filter['keyword'];
+                if (!empty($keyword)) {
+                    $query->where('a.post_title', 'like', "%$keyword%");
+                }
+
+                if ($isPage) {
+                    $query->where('a.post_type', 2);
+                } else {
+                    $query->where('a.post_type', 1);
+                }
+            })
+            ->order('update_time', 'DESC')
+            ->paginate(10);
+
+        return $articles;
+
+    }
+
+    /**
+     * 已发布文章查询
+     * @param  int $postId     文章id
+     * @param int  $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+                'post.id'          => $postId
+            ];
+
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->where($where)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->find();
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+                'relation.post_id'     => $postId
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 上一篇文章
+     * @param int $postId     文章id
+     * @param int $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedPrevArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+            ];
+
+            $article = $portalPostModel
+                ->alias('post')
+                ->field('post.*')
+                ->where($where)
+                ->where('post.id', '<', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'DESC')
+                ->find();
+
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel
+                ->alias('post')
+                ->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('relation.post_id', '<', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'DESC')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 下一篇文章
+     * @param int $postId     文章id
+     * @param int $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedNextArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+            ];
+
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->where($where)
+                ->where('post.id', '>', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'ASC')
+                ->find();
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('relation.post_id', '>', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'ASC')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 页面管理查询
+     * @param int $pageId 文章id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedPage($pageId)
+    {
+
+        $where = [
+            'post_type'   => 2,
+            'post_status' => 1,
+            'delete_time' => 0,
+            'id'          => $pageId
+        ];
+
+        $portalPostModel = new PortalPostModel();
+        $page            = $portalPostModel
+            ->where($where)
+            ->where('published_time', ['< time', time()], ['> time', 0], 'and')
+            ->find();
+
+        return $page;
+    }
+
+}

+ 372 - 0
app/portal/taglib/Portal.php

@@ -0,0 +1,372 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\taglib;
+
+use think\template\TagLib;
+
+class Portal extends TagLib
+{
+    /**
+     * 定义标签列表
+     */
+    protected $tags = [
+        // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次
+        'articles'         => ['attr' => 'field,where,limit,order,page,relation,returnVarName,pageVarName,categoryIds', 'close' => 1],//非必须属性item
+        'tagarticles'      => ['attr' => 'field,where,limit,order,page,relation,returnVarName,pageVarName,tagId', 'close' => 1],//非必须属性item
+        'page'             => ['attr' => 'id', 'close' => 1],//非必须属性item
+        'breadcrumb'       => ['attr' => 'cid', 'close' => 1],//非必须属性self
+        'categories'       => ['attr' => 'ids,where,order', 'close' => 1],//非必须属性item
+        'category'         => ['attr' => 'id', 'close' => 1],//非必须属性item
+        'subcategories'    => ['attr' => 'categoryId', 'close' => 1],//非必须属性item
+        'allsubcategories' => ['attr' => 'categoryId', 'close' => 1],//非必须属性item
+    ];
+
+    /**
+     * 文章列表标签
+     */
+    public function tagArticles($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? 'post.published_time DESC' : $tag['order'];
+        $relation      = empty($tag['relation']) ? '' : $tag['relation'];
+        $pageVarName   = empty($tag['pageVarName']) ? '__PAGE_VAR_NAME__' : $tag['pageVarName'];
+        $returnVarName = empty($tag['returnVarName']) ? 'articles_data' : $tag['returnVarName'];
+
+        $field = "''";
+        if (!empty($tag['field'])) {
+            if (strpos($tag['field'], '$') === 0) {
+                $field = $tag['field'];
+                $this->autoBuildVar($field);
+            } else {
+                $field = "'{$tag['field']}'";
+            }
+        }
+
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $limit = "''";
+        if (!empty($tag['limit'])) {
+            if (strpos($tag['limit'], '$') === 0) {
+                $limit = $tag['limit'];
+                $this->autoBuildVar($limit);
+            } else {
+                $limit = "'{$tag['limit']}'";
+            }
+        }
+
+        $page = "''";
+        if (!empty($tag['page'])) {
+            if (strpos($tag['page'], '$') === 0) {
+                $page = $tag['page'];
+                $this->autoBuildVar($page);
+            } else {
+                $page = intval($tag['page']);
+                $page = "'{$page}'";
+            }
+        }
+
+        $categoryIds = "''";
+        if (!empty($tag['categoryIds'])) {
+            if (strpos($tag['categoryIds'], '$') === 0) {
+                $categoryIds = $tag['categoryIds'];
+                $this->autoBuildVar($categoryIds);
+            } else {
+                $categoryIds = "'{$tag['categoryIds']}'";
+            }
+        }
+
+        if (!empty($order) && strpos($order, '$') === 0) {
+            $this->autoBuildVar($order);
+        } else {
+            $order = "'{$order}'";
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::articles([
+    'field'   => {$field},
+    'where'   => {$where},
+    'limit'   => {$limit},
+    'order'   => {$order},
+    'page'    => {$page},
+    'relation'=> '{$relation}',
+    'category_ids'=>{$categoryIds}
+]);
+
+\${$pageVarName} = isset(\${$returnVarName}['page'])?\${$returnVarName}['page']:'';
+
+ ?>
+<volist name="{$returnVarName}.articles" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 标签文章列表标签
+     */
+    public function tagTagArticles($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? 'post.published_time DESC' : $tag['order'];
+        $relation      = empty($tag['relation']) ? '' : $tag['relation'];
+        $pageVarName   = empty($tag['pageVarName']) ? '__PAGE_VAR_NAME__' : $tag['pageVarName'];
+        $returnVarName = empty($tag['returnVarName']) ? 'tag_articles_data' : $tag['returnVarName'];
+
+        $field = "''";
+        if (!empty($tag['field'])) {
+            if (strpos($tag['field'], '$') === 0) {
+                $field = $tag['field'];
+                $this->autoBuildVar($field);
+            } else {
+                $field = "'{$tag['field']}'";
+            }
+        }
+
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $limit = "''";
+        if (!empty($tag['limit'])) {
+            if (strpos($tag['limit'], '$') === 0) {
+                $limit = $tag['limit'];
+                $this->autoBuildVar($limit);
+            } else {
+                $limit = "'{$tag['limit']}'";
+            }
+        }
+
+        $page = "''";
+        if (!empty($tag['page'])) {
+            if (strpos($tag['page'], '$') === 0) {
+                $page = $tag['page'];
+                $this->autoBuildVar($page);
+            } else {
+                $page = intval($tag['page']);
+                $page = "'{$page}'";
+            }
+        }
+
+        $tagId = "''";
+        if (!empty($tag['tagId'])) {
+            if (strpos($tag['tagId'], '$') === 0) {
+                $tagId = $tag['tagId'];
+                $this->autoBuildVar($tagId);
+            } else {
+                $tagId = "'{$tag['tagId']}'";
+            }
+        }
+
+        if (strpos($order, '$') === 0) {
+            $this->autoBuildVar($order);
+        } else {
+            $order = "'{$order}'";
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::tagArticles([
+    'field'   => {$field},
+    'where'   => {$where},
+    'limit'   => {$limit},
+    'order'   => {$order},
+    'page'    => {$page},
+    'relation'=> '{$relation}',
+    'tag_id'=>{$tagId}
+]);
+
+\${$pageVarName} = isset(\${$returnVarName}['page'])?\${$returnVarName}['page']:'';
+
+ ?>
+<volist name="{$returnVarName}.articles" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 单页文章标签
+     */
+    public function tagPage($tag, $content)
+    {
+        $id = empty($tag['id']) ? 0 : $tag['id'];
+        if (strpos($id, '$') === 0) {
+            $this->autoBuildVar($id);
+        }
+        $returnVarName = empty($tag['item']) ? 'portal_page' : $tag['item'];
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::page({$id});
+?>
+{$content}
+parse;
+        return $parse;
+    }
+
+    /**
+     * 面包屑标签
+     */
+    public function tagBreadcrumb($tag, $content)
+    {
+        $cid = empty($tag['cid']) ? '0' : $tag['cid'];
+
+        if (!empty($cid)) {
+            $this->autoBuildVar($cid);
+        }
+
+        $self = isset($tag['self']) ? $tag['self'] : 'false';
+
+        $parse = <<<parse
+<?php
+if(!empty({$cid})){
+    \$__BREADCRUMB_ITEMS__ = \app\portal\service\ApiService::breadcrumb({$cid},{$self});
+?>
+
+<volist name="__BREADCRUMB_ITEMS__" id="vo">
+    {$content}
+</volist>
+
+<?php
+}
+?>
+parse;
+
+        return $parse;
+
+    }
+
+    /**
+     * 文章分类标签
+     */
+    public function tagCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? '' : $tag['order'];
+        $ids           = empty($tag['ids']) ? '""' : $tag['ids'];
+        $returnVarName = 'portal_categories_data';
+        if (strpos($ids, '$') === 0) {
+            $this->autoBuildVar($ids);
+        }
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::categories([
+    'where'   => {$where},
+    'order'   => '{$order}',
+    'ids'     => {$ids}
+]);
+
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 文章分类详情标签
+     * @param array  $tag
+     * @param string $content
+     * @return string
+     */
+    public function tagCategory($tag, $content)
+    {
+        $id = empty($tag['id']) ? 0 : $tag['id'];
+        if (strpos($id, '$') === 0) {
+            $this->autoBuildVar($id);
+        }
+        $returnVarName = empty($tag['item']) ? 'portal_category' : $tag['item'];
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::category({$id});
+?>
+{$content}
+parse;
+        return $parse;
+    }
+
+    /**
+     * 文章子分类标签
+     */
+    public function tagSubCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $returnVarName = 'portal_sub_categories_data';
+
+        $categoryId = "0";
+        if (!empty($tag['categoryId'])) {
+            if (strpos($tag['categoryId'], '$') === 0) {
+                $categoryId = $tag['categoryId'];
+                $this->autoBuildVar($categoryId);
+            } else {
+                $categoryId = intval($tag['categoryId']);
+                $categoryId = "{$categoryId}";
+            }
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::subCategories({$categoryId});
+ 
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+
+    /**
+     * 文章分类所有子分类标签
+     */
+    public function tagAllSubCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $returnVarName = 'portal_all_sub_categories_data';
+
+        $categoryId = "0";
+        if (!empty($tag['categoryId'])) {
+            if (strpos($tag['categoryId'], '$') === 0) {
+                $categoryId = $tag['categoryId'];
+                $this->autoBuildVar($categoryId);
+            } else {
+                $categoryId = intval($tag['categoryId']);
+                $categoryId = "{$categoryId}";
+            }
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::allSubCategories({$categoryId});
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+}

+ 53 - 0
app/portal/url.php

@@ -0,0 +1,53 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'List/index'    => [
+        'name'   => '门户应用-文章列表',
+        'vars'   => [
+            'id' => [
+                'pattern' => '\d+',
+                'require' => true
+            ]
+        ],
+        'simple' => true
+    ],
+    'Page/index'    => [
+        'name'   => '门户应用-页面页',
+        'vars'   => [
+            'id' => [
+                'pattern' => '\d+',
+                'require' => true
+            ]
+        ],
+        'simple' => true
+    ],
+    'Article/index' => [
+        'name'   => '门户应用-文章页',
+        'vars'   => [
+            'id'  => [
+                'pattern' => '\d+',
+                'require' => true
+            ],
+            'cid' => [
+                'pattern' => '\d+',
+                'require' => false
+            ]
+        ],
+        'simple' => true
+    ],
+    'Search/index'  => [
+        'name'   => '门户应用-搜索页',
+        'vars'   => [
+
+        ],
+        'simple' => false
+    ],
+];

+ 15 - 0
app/portal/user_action.php

@@ -0,0 +1,15 @@
+<?php
+return [
+//    'test' => [
+//        'name'          => '用户登录',//用户操作名称
+//        'score'         => 1,//更改积分,可以为负
+//        'coin'          => 0,//更改金币,可以为负
+//        'cycle_time'    => 1,//周期时间值
+//        'cycle_type'    => 1,//周期类型;0:不限;1:按天;2:按小时;3:永久
+//        'reward_number' => 1,//奖励次数
+//        'url'           => [
+//            'action' => 'portal/Test/test',
+//            'param'  => ['id' => 1]
+//        ],//执行操作的url
+//    ]
+];

+ 30 - 0
app/portal/validate/AdminArticleValidate.php

@@ -0,0 +1,30 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\validate;
+
+use think\Validate;
+
+class AdminArticleValidate extends Validate
+{
+    protected $rule = [
+        'categories' => 'require',
+        'post_title' => 'require',
+    ];
+    protected $message = [
+        'categories.require' => '请指定文章分类!',
+        'post_title.require' => '文章标题不能为空!',
+    ];
+
+    protected $scene = [
+//        'add'  => ['user_login,user_pass,user_email'],
+//        'edit' => ['user_login,user_email'],
+    ];
+}

+ 51 - 0
app/portal/validate/AdminPageValidate.php

@@ -0,0 +1,51 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\validate;
+
+use app\admin\model\RouteModel;
+use think\Validate;
+
+class AdminPageValidate extends Validate
+{
+    protected $rule = [
+        'post_title' => 'require',
+        'post_alias' => 'checkAlias'
+    ];
+    protected $message = [
+        'post_title.require' => '页面标题不能为空',
+    ];
+
+    protected $scene = [
+//        'add'  => ['user_login,user_pass,user_email'],
+//        'edit' => ['user_login,user_email'],
+    ];
+
+    // 自定义验证规则
+    protected function checkAlias($value, $rule, $data)
+    {
+        if (empty($value)) {
+            return true;
+        }
+
+        if (preg_match("/^\d+$/", $value)) {
+            return "别名不能为纯数字!";
+        }
+
+        $routeModel = new RouteModel();
+        $fullUrl    = $routeModel->buildFullUrl('portal/Page/index', ['id' => $data['id']]);
+        if (!$routeModel->existsRoute($value, $fullUrl)) {
+            return true;
+        } else {
+            return "别名已经存在!";
+        }
+
+    }
+}

+ 55 - 0
app/portal/validate/PortalCategoryValidate.php

@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\validate;
+
+use app\admin\model\RouteModel;
+use think\Validate;
+
+class PortalCategoryValidate extends Validate
+{
+    protected $rule = [
+        'name'  => 'require',
+        'alias' => 'checkAlias',
+    ];
+    protected $message = [
+        'name.require' => '分类名称不能为空',
+    ];
+
+    protected $scene = [
+//        'add'  => ['user_login,user_pass,user_email'],
+//        'edit' => ['user_login,user_email'],
+    ];
+
+    // 自定义验证规则
+    protected function checkAlias($value, $rule, $data)
+    {
+        if (empty($value)) {
+            return true;
+        }
+
+        if (preg_match("/^\d+$/", $value)) {
+            return "别名不能为纯数字!";
+        }
+
+        $routeModel = new RouteModel();
+        if (isset($data['id']) && $data['id'] > 0) {
+            $fullUrl = $routeModel->buildFullUrl('portal/List/index', ['id' => $data['id']]);
+        } else {
+            $fullUrl = $routeModel->getFullUrlByUrl($data['alias']);
+        }
+        if (!$routeModel->existsRoute($value, $fullUrl)) {
+            return true;
+        } else {
+            return "别名已经存在!";
+        }
+
+    }
+}

+ 210 - 0
app/test/controller/IndexController.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace app\test\controller;
+
+use QL\QueryList;
+use think\Db;
+use cmf\controller\HomeBaseController;
+
+class IndexController extends HomeBaseController
+{
+    public function index()
+    {
+        header("Content-type: application/vnd.ms-excel");
+        header("Content-Disposition: attachment;Filename=报表.xls");
+        $data = Db::table('recruit_entry')->where('rid', 6)->select()->toArray();
+
+        $posts    = Db::table('recruit_post')->column('code,name', 'id');
+        $recruits = Db::table('recruit')->column('name', 'id');
+
+        $families_data = Db::table('recruit_family')->select()->toArray();
+        $families      = [];
+        foreach ($families_data as $v) {
+            if (empty($families[$v['eid']])) {
+                $families[$v['eid']] = '';
+            }
+            if (!empty($v['appellation'])) {
+                $families[$v['eid']] .= $v['appellation'] . '-' . $v['realname'] . '-' . date('Y-m-d', $v['birthday']) . '-' . $v['political_attitudes'] . '-' . $v['work'] . ';';
+            }
+        }
+
+        foreach ($data as &$v) {
+            $v['post_code']     = $posts[$v['post_id']]['code'];
+            $v['post_name']     = $posts[$v['post_id']]['name'];
+            $v['recruits_name'] = $recruits[$v['rid']];
+            $v['family']        = empty($families[$v['id']]) ? '' : $families[$v['id']];
+            $v['birthday']      = empty($v['birthday']) ? '' : date('Y-m-d', $v['birthday']);
+            $v['status_text']   = $v['status'] == 1 ? '审核中或报名成功' : '暂存或审核失败';
+            $v['word_url']      = empty($v['word_url']) ? '' : 'http://www.jucai.gov.cn' . $v['word_url'];
+            $v['front_card']    = $this->_dealImage($v['front_card']);
+            $v['back_card']     = $this->_dealImage($v['back_card']);
+            $v['diploma_img']   = $this->_dealImage($v['diploma_img']);
+            $v['degree_img']    = $this->_dealImage($v['degree_img']);
+            $v['xuexin_img']    = $this->_dealImage($v['xuexin_img']);
+            $v['work_pro_img']  = $this->_dealImage($v['work_pro_img']);
+            $v['work_approve']  = $this->_dealImage($v['work_approve']);
+
+            unset($v);
+        }
+
+        $this->assign('data', $data);
+        return $this->fetch();
+    }
+
+    private function _dealImage($image)
+    {
+        $arr = explode(',', $image);
+        $res = [];
+        foreach ($arr as $v) {
+            if (strpos($v, 'blob:') === false) {
+                $res[] = $v;
+            }
+        }
+
+        if (empty($res)) {
+            return '';
+        } else {
+            return implode('<br/><br/>', $res);
+        }
+    }
+
+    public function t1()
+    {
+        $table = QueryList::get('http://rcyz.qxrc.com/index.php?g=portal&m=index&a=site_list')->find('table');
+
+// 采集表的每行内容
+        $tableRows = $table->find('tr:gt(0)')->map(function ($row) {
+            $arr          = [];
+            $arr['title'] = $row->find('td:eq(2) a')->text();
+            $arr['href']  = 'http://rcyz.qxrc.com' . $row->find('td:eq(2) a')->attr('href');
+            $arr['id']    = str_replace('http://rcyz.qxrc.com/index.php?g=activity&m=activity&a=site_portal&id=', '', $arr['href']);
+
+            return $arr;
+        });
+
+        $res = $tableRows->all();
+        foreach ($res as $v) {
+            Db::table('psite')->insert([
+                'id'   => $v['id'],
+                'href' => $v['href'],
+            ]);
+        }
+        return 'ok';
+    }
+
+    public function t2()
+    {
+        $site = Db::table('psite')->select();
+        foreach ($site as $v) {
+            $ql = QueryList::get($v['href']);
+
+            $rt['town']    = str_replace('所在乡镇:', '', $ql->find('.head h4')->text());
+            $rt['content'] = $ql->find('.intro-content')->text();
+            $rt['contact'] = $ql->find('.comment-ranking p:eq(0) strong')->text();
+            $rt['mobile']  = $ql->find('.comment-ranking p:eq(1) strong')->text();
+            $rt['email']   = $ql->find('.comment-ranking p:eq(2) strong')->text();
+            $rt['address'] = $ql->find('.comment-ranking p:eq(5) strong')->text();
+
+            Db::name('activity_site')->where('id', $v['id'])->update($rt);
+        }
+    }
+
+    public function t3()
+    {
+        set_time_limit(0);
+        $site = Db::table('psite')->select();
+        foreach ($site as $v) {
+            $this->_getActivity($v['href'], $v['id']);
+        }
+    }
+
+    private function _getActivity($url, $id, $page = 1)
+    {
+        $rt   = QueryList::get($url . '&p=' . $page)->rules([
+            'href'  => ['.item-image a', 'href'],
+            'image' => ['.item-image img', 'src'],
+        ])->range('.row .span9 .span3')->query()->getData();
+        $flag = QueryList::get($url . '&p=' . $page)->find('.pagination li:last')->text();
+        $list = $rt->all();
+        if (!empty($list)) {
+            foreach ($list as $v) {
+                Db::table('pactivity')->insert([
+                    'site_id' => $id,
+                    'href'    => 'http://rcyz.qxrc.com' . $v['href'],
+                    'image'   => $v['image'],
+                ]);
+            }
+
+        }
+
+        if ($flag == '尾页') {
+            $this->_getActivity($url, $id, $page + 1);
+        }
+    }
+
+    public function t4()
+    {
+        set_time_limit(0);
+        $list = Db::table('pactivity')->select();
+        foreach ($list as $v) {
+            $ql                 = QueryList::get($v['href']);
+            $arr                = [];
+            $arr['id']          = (int)str_replace('http://rcyz.qxrc.com/index.php?g=activity&m=activity&a=detail&id=', '', $v['href']);
+            $arr['title']       = $ql->find('.article-box h2')->text();
+            $arr['main_image']  = (string)$v['image'];
+            $arr['start_time']  = strtotime($ql->find('.tc-box:eq(3)>div:eq(1)>div:eq(0) strong')->text());
+            $end_time           = strtotime($ql->find('.tc-box:eq(3)>div:eq(1)>div:eq(1) strong')->text());
+            $arr['end_time']    = $end_time > 0 ? $end_time : 0;
+            $arr['address']     = $ql->find('.tc-box:eq(4)>div:eq(1) strong')->text();
+            $arr['user_id']     = $v['site_id'];
+            $arr['content']     = $ql->find('#article_content')->html();
+            $info               = $ql->find('.article-infobox span:eq(0)')->text();
+            $info               = explode('|', $info);
+            $arr['create_time'] = strtotime(trim(str_replace('创建于:', '', $info[0])));
+            $arr['author']      = trim(str_replace('作者:', '', $info[1]));
+            $arr['status']      = 2;
+            Db::name('activity')->insert($arr);
+        }
+        return 'ok';
+    }
+
+    public function t5()
+    {
+        $ql                 = QueryList::get('http://rcyz.qxrc.com/index.php?g=portal&m=index&a=review_list&p=1');
+        $this->_getReviewList($ql);
+        $ql                 = QueryList::get('http://rcyz.qxrc.com/index.php?g=portal&m=index&a=review_list&p=2');
+        $this->_getReviewList($ql);
+    }
+    private function _getReviewList(QueryList $ql)
+    {
+        $rt   = $ql->rules([
+            'href'  => ['.item-image a', 'href'],
+            'image' => ['.item-image img', 'src'],
+        ])->range('.tc-gridbox')->query()->getData();
+
+        $list = $rt->all();
+        foreach ($list as $v) {
+            Db::table('preview')->insert([
+                'href'    => 'http://rcyz.qxrc.com' . $v['href'],
+                'image'   => $v['image'],
+            ]);
+        }
+    }
+
+    public function t6()
+    {
+        $list = Db::table('preview')->select();
+        foreach ($list as $v) {
+            $ql                 = QueryList::get($v['href']);
+            $arr                = [];
+            $arr['title'] = $ql->find('.article-box>h2')->text();
+            $arr['content'] = $ql->find('#article_content')->html();
+            $arr['main_image'] = $v['image'];
+            $href = $ql->find('.row>.span3>div:eq(0)>div:eq(1)>div:eq(0) a')->attr('href');
+            $arr['activity_id'] = (int)str_replace('/index.php?g=activity&m=activity&a=detail&id=','',$href);
+            $arr['user_id'] = (int)Db::name('activity')->where('id',$arr['activity_id'])->value('user_id');
+            $arr['create_time'] = time();
+            Db::name('activity_review')->insert($arr);
+        }
+    }
+}

+ 44 - 0
composer.json

@@ -0,0 +1,44 @@
+{
+    "name": "thinkcmf/thinkcmf",
+    "description": "ThinkCMF based on ThinkPHP 5.1 , it is a free and open source Content Management Framework(CMF)",
+    "type": "project",
+    "keywords": [
+        "cmf",
+        "thinkcmf",
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://www.thinkcmf.com/",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "thinkcmf",
+            "email": "catman@thinkcmf.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.6.0",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*",
+        "topthink/think-helper": "^1.0",
+        "topthink/think-image": "^1.0",
+        "thinkcmf/cmf-app": "~5.1.0",
+        "thinkcmf/cmf-api": "~5.1.0",
+        "jaeger/querylist": "^4.2"
+    },
+    "extra": {
+        "think-path": "vendor/thinkphp",
+        "think-config": "data/config"
+    },
+    "config": {
+        "vendor-dir": "vendor"
+    },
+    "repositories": {
+        "packagist": {
+            "type": "composer",
+            "url": "https://packagist.phpcomposer.com"
+        }
+    }
+}

+ 1883 - 0
composer.lock

@@ -0,0 +1,1883 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "0bce119ae0725fd7de2c9b1a5d363701",
+    "packages": [
+        {
+            "name": "cache/adapter-common",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/adapter-common.git",
+                "reference": "6320bb5f5574cb88438059b59f8708da6b6f1d32"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/6320bb5f5574cb88438059b59f8708da6b6f1d32",
+                "reference": "6320bb5f5574cb88438059b59f8708da6b6f1d32",
+                "shasum": ""
+            },
+            "require": {
+                "cache/tag-interop": "^1.0",
+                "php": "^5.6 || ^7.0",
+                "psr/cache": "^1.0",
+                "psr/log": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "^0.16",
+                "phpunit/phpunit": "^5.7.21"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\Adapter\\Common\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Scherer",
+                    "email": "aequasi@gmail.com",
+                    "homepage": "https://github.com/aequasi"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                }
+            ],
+            "description": "Common classes for PSR-6 adapters",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "psr-6",
+                "tag"
+            ],
+            "time": "2018-07-08T13:04:33+00:00"
+        },
+        {
+            "name": "cache/filesystem-adapter",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/filesystem-adapter.git",
+                "reference": "d50680b6dabbe39f9831f5fc9efa61c09d936017"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/d50680b6dabbe39f9831f5fc9efa61c09d936017",
+                "reference": "d50680b6dabbe39f9831f5fc9efa61c09d936017",
+                "shasum": ""
+            },
+            "require": {
+                "cache/adapter-common": "^1.0",
+                "league/flysystem": "^1.0",
+                "php": "^5.6 || ^7.0",
+                "psr/cache": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "provide": {
+                "psr/cache-implementation": "^1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "^0.16",
+                "phpunit/phpunit": "^5.7.21"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\Adapter\\Filesystem\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Scherer",
+                    "email": "aequasi@gmail.com",
+                    "homepage": "https://github.com/aequasi"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                }
+            ],
+            "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "filesystem",
+                "psr-6",
+                "tag"
+            ],
+            "time": "2017-07-16T21:09:25+00:00"
+        },
+        {
+            "name": "cache/tag-interop",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/tag-interop.git",
+                "reference": "c7496dd81530f538af27b4f2713cde97bc292832"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/c7496dd81530f538af27b4f2713cde97bc292832",
+                "reference": "c7496dd81530f538af27b4f2713cde97bc292832",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5 || ^7.0",
+                "psr/cache": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\TagInterop\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com",
+                    "homepage": "https://github.com/nicolas-grekas"
+                }
+            ],
+            "description": "Framework interoperable interfaces for tags",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr6",
+                "tag"
+            ],
+            "time": "2017-03-13T09:14:27+00:00"
+        },
+        {
+            "name": "electrolinux/phpquery",
+            "version": "0.9.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/electrolinux/phpquery.git",
+                "reference": "6cb8afcfe8cd4ce45f2f8c27d561383037c27a3a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/electrolinux/phpquery/zipball/6cb8afcfe8cd4ce45f2f8c27d561383037c27a3a",
+                "reference": "6cb8afcfe8cd4ce45f2f8c27d561383037c27a3a",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "phpQuery/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Tobiasz Cudnik",
+                    "email": "tobiasz.cudnik@gmail.com",
+                    "homepage": "https://github.com/TobiaszCudnik",
+                    "role": "Developer"
+                },
+                {
+                    "name": "didier Belot",
+                    "role": "Packager"
+                }
+            ],
+            "description": "phpQuery is a server-side, chainable, CSS3 selector driven Document Object Model (DOM) API based on jQuery JavaScript Library",
+            "homepage": "http://code.google.com/p/phpquery/",
+            "time": "2013-03-21T12:39:33+00:00"
+        },
+        {
+            "name": "ezyang/htmlpurifier",
+            "version": "v4.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ezyang/htmlpurifier.git",
+                "reference": "a617e55bc62a87eec73bd456d146d134ad716f03"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a617e55bc62a87eec73bd456d146d134ad716f03",
+                "reference": "a617e55bc62a87eec73bd456d146d134ad716f03",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2"
+            },
+            "require-dev": {
+                "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
+                "files": [
+                    "library/HTMLPurifier.composer.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-or-later"
+            ],
+            "authors": [
+                {
+                    "name": "Edward Z. Yang",
+                    "email": "admin@htmlpurifier.org",
+                    "homepage": "http://ezyang.com"
+                }
+            ],
+            "description": "Standards compliant HTML filter written in PHP",
+            "homepage": "http://htmlpurifier.org/",
+            "keywords": [
+                "html"
+            ],
+            "time": "2019-10-28T03:44:26+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "7.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79",
+                "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.4",
+                "guzzlehttp/psr7": "^1.7",
+                "php": "^7.2.5 || ^8.0",
+                "psr/http-client": "^1.0"
+            },
+            "provide": {
+                "psr/http-client-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "php-http/client-integration-tests": "^3.0",
+                "phpunit/phpunit": "^8.5.5 || ^9.3.5",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "ext-curl": "Required for CURL handler support",
+                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "7.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Márk Sági-Kazár",
+                    "email": "mark.sagikazar@gmail.com",
+                    "homepage": "https://sagikazarmark.hu"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "psr-18",
+                "psr-7",
+                "rest",
+                "web service"
+            ],
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/Nyholm",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/alexeyshockov",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/gmponos",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-10T11:47:56+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "60d379c243457e073cff02bc323a2a86cb355631"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
+                "reference": "60d379c243457e073cff02bc323a2a86cb355631",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^4.4 || ^5.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2020-09-30T07:37:28+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
+                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2020-09-30T07:37:11+00:00"
+        },
+        {
+            "name": "jaeger/g-http",
+            "version": "V1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/GHttp.git",
+                "reference": "9639bd2b24367eae32dba7f4d4a231ae8ea0a0bb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/9639bd2b24367eae32dba7f4d4a231ae8ea0a0bb",
+                "reference": "9639bd2b24367eae32dba7f4d4a231ae8ea0a0bb",
+                "shasum": ""
+            },
+            "require": {
+                "cache/filesystem-adapter": "^1.0",
+                "guzzlehttp/guzzle": "^7.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jaeger\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaeger",
+                    "email": "JaegerCode@gmail.com"
+                }
+            ],
+            "description": "Simple Http client base on GuzzleHttp",
+            "time": "2020-09-27T08:23:30+00:00"
+        },
+        {
+            "name": "jaeger/phpquery-single",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/phpQuery-single.git",
+                "reference": "fb80b4a0e5a337438d26e061ec3fb725c9f2a116"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/fb80b4a0e5a337438d26e061ec3fb725c9f2a116",
+                "reference": "fb80b4a0e5a337438d26e061ec3fb725c9f2a116",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "phpQuery.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Tobiasz Cudnik",
+                    "email": "tobiasz.cudnik@gmail.com",
+                    "homepage": "https://github.com/TobiaszCudnik",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Jaeger",
+                    "role": "Packager"
+                }
+            ],
+            "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/",
+            "homepage": "http://code.google.com/p/phpquery/",
+            "time": "2019-11-05T01:50:34+00:00"
+        },
+        {
+            "name": "jaeger/querylist",
+            "version": "V4.2.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/QueryList.git",
+                "reference": "465c6aefc7ca08c4d81de2556ab765918c0a35ce"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/465c6aefc7ca08c4d81de2556ab765918c0a35ce",
+                "reference": "465c6aefc7ca08c4d81de2556ab765918c0a35ce",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "jaeger/g-http": "^1.1",
+                "jaeger/phpquery-single": "^1",
+                "php": ">=7.1",
+                "tightenco/collect": "^8.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5",
+                "symfony/var-dumper": "^3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "QL\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaeger",
+                    "email": "JaegerCode@gmail.com"
+                }
+            ],
+            "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
+            "homepage": "http://querylist.cc",
+            "keywords": [
+                "QueryList",
+                "phpQuery",
+                "spider"
+            ],
+            "funding": [
+                {
+                    "url": "https://opencollective.com/querylist",
+                    "type": "open_collective"
+                }
+            ],
+            "time": "2020-09-27T09:41:44+00:00"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "9be3b16c877d477357c015cec057548cf9b2a14a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a",
+                "reference": "9be3b16c877d477357c015cec057548cf9b2a14a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "league/mime-type-detection": "^1.3",
+                "php": "^7.2.5 || ^8.0"
+            },
+            "conflict": {
+                "league/flysystem-sftp": "<1.0.6"
+            },
+            "require-dev": {
+                "phpspec/prophecy": "^1.11.1",
+                "phpunit/phpunit": "^8.5.8"
+            },
+            "suggest": {
+                "ext-fileinfo": "Required for MimeType",
+                "ext-ftp": "Allows you to use FTP server storage",
+                "ext-openssl": "Allows you to use FTPS server storage",
+                "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+                "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+                "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+                "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+                "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+                "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+                "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+                "league/flysystem-webdav": "Allows you to use WebDAV storage",
+                "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+                "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+                "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Filesystem abstraction: Many filesystems, one API.",
+            "keywords": [
+                "Cloud Files",
+                "WebDAV",
+                "abstraction",
+                "aws",
+                "cloud",
+                "copy.com",
+                "dropbox",
+                "file systems",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "rackspace",
+                "remote",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "funding": [
+                {
+                    "url": "https://offset.earth/frankdejonge",
+                    "type": "other"
+                }
+            ],
+            "time": "2020-08-23T07:39:11+00:00"
+        },
+        {
+            "name": "league/mime-type-detection",
+            "version": "1.5.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/mime-type-detection.git",
+                "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/353f66d7555d8a90781f6f5e7091932f9a4250aa",
+                "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "php": "^7.2 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^0.12.36",
+                "phpunit/phpunit": "^8.5.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "League\\MimeTypeDetection\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frankdejonge.nl"
+                }
+            ],
+            "description": "Mime-type detection for Flysystem",
+            "funding": [
+                {
+                    "url": "https://github.com/frankdejonge",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-18T11:50:25+00:00"
+        },
+        {
+            "name": "mindplay/annotations",
+            "version": "1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-annotations/php-annotations.git",
+                "reference": "77ef66f79fb65a7b7e7e005be0bd6b643de43867"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-annotations/php-annotations/zipball/77ef66f79fb65a7b7e7e005be0bd6b643de43867",
+                "reference": "77ef66f79fb65a7b7e7e005be0bd6b643de43867",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/php-code-coverage": "~1.2.1",
+                "phpunit/php-file-iterator": ">=1.3.0@stable"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "mindplay\\annotations\\": "src\\annotations"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0+"
+            ],
+            "authors": [
+                {
+                    "name": "Rasmus Schultz",
+                    "email": "rasmus@mindplay.dk"
+                }
+            ],
+            "description": "Industrial-strength annotations for PHP",
+            "homepage": "http://blog.mindplay.dk/",
+            "keywords": [
+                "annotations",
+                "framework"
+            ],
+            "time": "2019-01-15T17:03:08+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "c5e61d0729507049cec9673aa1a679f9adefd683"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c5e61d0729507049cec9673aa1a679f9adefd683",
+                "reference": "c5e61d0729507049cec9673aa1a679f9adefd683",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "^1.2",
+                "friendsofphp/php-cs-fixer": "^2.2",
+                "phpunit/phpunit": "^4.8 || ^5.7"
+            },
+            "suggest": {
+                "ext-mbstring": "Needed to send email in multibyte encoding charset",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "time": "2019-12-10T11:17:38+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/http-client",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-client.git",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0 || ^8.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Client\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP clients",
+            "homepage": "https://github.com/php-fig/http-client",
+            "keywords": [
+                "http",
+                "http-client",
+                "psr",
+                "psr-18"
+            ],
+            "time": "2020-06-29T06:28:15+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2020-03-23T09:12:05+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.18.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.18.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
+                "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/var-dumper",
+            "version": "v5.1.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "c976c115a0d788808f7e71834c8eb0844f678d02"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c976c115a0d788808f7e71834c8eb0844f678d02",
+                "reference": "c976c115a0d788808f7e71834c8eb0844f678d02",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<5.4.3",
+                "symfony/console": "<4.4"
+            },
+            "require-dev": {
+                "ext-iconv": "*",
+                "symfony/console": "^4.4|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "twig/twig": "^2.4|^3.0"
+            },
+            "suggest": {
+                "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+                "ext-intl": "To show region name in time zone dump",
+                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+            },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.1-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony mechanism for exploring and dumping PHP variables",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-09-18T14:27:32+00:00"
+        },
+        {
+            "name": "thinkcmf/cmf",
+            "version": "v5.1.11",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thinkcmf/cmf-core.git",
+                "reference": "f9e726abc3b6412bdce8b5d1e426e40bb2932380"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thinkcmf/cmf-core/zipball/f9e726abc3b6412bdce8b5d1e426e40bb2932380",
+                "reference": "f9e726abc3b6412bdce8b5d1e426e40bb2932380",
+                "shasum": ""
+            },
+            "require": {
+                "electrolinux/phpquery": "^0.9.6",
+                "ezyang/htmlpurifier": "^4.9",
+                "mindplay/annotations": "^1.3",
+                "phpmailer/phpmailer": "~6.0",
+                "thinkcmf/cmf-extend": "~5.1.0",
+                "topthink/framework": "~5.1.0",
+                "topthink/think-captcha": "^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "cmf\\": "src"
+                },
+                "files": [
+                    "src/common.php"
+                ],
+                "classmap": [
+                    "src/App.php",
+                    "src/Log.php",
+                    "src/route/dispatch/Module.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "catman",
+                    "email": "catman@thinkcmf.com"
+                }
+            ],
+            "description": "The ThinkCMF Core Package",
+            "time": "2019-12-16T09:51:55+00:00"
+        },
+        {
+            "name": "thinkcmf/cmf-api",
+            "version": "v5.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thinkcmf/cmf-api.git",
+                "reference": "a7d9edecc8c44aec6395dac8678a2577127f776d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thinkcmf/cmf-api/zipball/a7d9edecc8c44aec6395dac8678a2577127f776d",
+                "reference": "a7d9edecc8c44aec6395dac8678a2577127f776d",
+                "shasum": ""
+            },
+            "require": {
+                "thinkcmf/cmf": "~5.1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "api\\": "src"
+                },
+                "files": []
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "catman",
+                    "email": "catman@thinkcmf.com"
+                }
+            ],
+            "description": "The ThinkCMF 5.1 Core Api Package",
+            "time": "2019-07-23T23:29:11+00:00"
+        },
+        {
+            "name": "thinkcmf/cmf-app",
+            "version": "v5.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thinkcmf/cmf-app.git",
+                "reference": "dd02fc871dcfccc59ac21750ce6ddec359e79e7d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thinkcmf/cmf-app/zipball/dd02fc871dcfccc59ac21750ce6ddec359e79e7d",
+                "reference": "dd02fc871dcfccc59ac21750ce6ddec359e79e7d",
+                "shasum": ""
+            },
+            "require": {
+                "thinkcmf/cmf": "~5.1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "app\\": "src"
+                },
+                "files": []
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "catman",
+                    "email": "catman@thinkcmf.com"
+                }
+            ],
+            "description": "The ThinkCMF App Package",
+            "time": "2019-09-06T03:58:33+00:00"
+        },
+        {
+            "name": "thinkcmf/cmf-extend",
+            "version": "v5.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thinkcmf/cmf-extend.git",
+                "reference": "39cbfdc69980a4f4b98ee82fa16c605004f26f5f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thinkcmf/cmf-extend/zipball/39cbfdc69980a4f4b98ee82fa16c605004f26f5f",
+                "reference": "39cbfdc69980a4f4b98ee82fa16c605004f26f5f",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "dir\\": "src/dir",
+                    "tree\\": "src/tree",
+                    "wxapp\\": "src/wxapp"
+                },
+                "files": []
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "catman",
+                    "email": "catman@thinkcmf.com"
+                }
+            ],
+            "description": "The ThinkCMF extend Package",
+            "time": "2019-03-04T05:15:23+00:00"
+        },
+        {
+            "name": "thinkcmf/cmf-install",
+            "version": "v5.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thinkcmf/cmf-install.git",
+                "reference": "651f7ff4d4413e2bcf98823853144bb2b67f671a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thinkcmf/cmf-install/zipball/651f7ff4d4413e2bcf98823853144bb2b67f671a",
+                "reference": "651f7ff4d4413e2bcf98823853144bb2b67f671a",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "app\\install\\": "src"
+                },
+                "files": []
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "catman",
+                    "email": "catman@thinkcmf.com"
+                }
+            ],
+            "description": "The ThinkCMF Install Package",
+            "time": "2019-07-23T23:26:40+00:00"
+        },
+        {
+            "name": "tightenco/collect",
+            "version": "v8.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tighten/collect.git",
+                "reference": "0d261a13c36fe964449d0240744eb4094ac3cd12"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tighten/collect/zipball/0d261a13c36fe964449d0240744eb4094ac3cd12",
+                "reference": "0d261a13c36fe964449d0240744eb4094ac3cd12",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "nesbot/carbon": "^2.23.0",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Collect/Support/helpers.php",
+                    "src/Collect/Support/alias.php"
+                ],
+                "psr-4": {
+                    "Tightenco\\Collect\\": "src/Collect"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylorotwell@gmail.com"
+                }
+            ],
+            "description": "Collect - Illuminate Collections as a separate package.",
+            "keywords": [
+                "collection",
+                "laravel"
+            ],
+            "time": "2020-09-14T22:38:08+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v5.1.39",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "5762858f3d58faafb3a39427f8788884b2927007"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/5762858f3d58faafb3a39427f8788884b2927007",
+                "reference": "5762858f3d58faafb3a39427f8788884b2927007",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6.0",
+                "topthink/think-installer": "2.*"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "^5.0|^6.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "2.*"
+            },
+            "type": "think-framework",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                },
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "the new thinkphp framework",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "time": "2019-11-17T23:22:02+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-captcha.git",
+                "reference": "54c8a51552f99ff9ea89ea9c272383a8f738ceee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-captcha/zipball/54c8a51552f99ff9ea89ea9c272383a8f738ceee",
+                "reference": "54c8a51552f99ff9ea89ea9c272383a8f738ceee",
+                "shasum": ""
+            },
+            "require": {
+                "topthink/framework": "5.1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp5",
+            "time": "2017-12-31T16:37:49+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "reference": "5f92178606c8ce131d36b37a57c58eb71e55f019",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\helper\\": "src"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Helper Package",
+            "time": "2018-10-05T00:43:21+00:00"
+        },
+        {
+            "name": "topthink/think-image",
+            "version": "v1.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-image.git",
+                "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512",
+                "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512",
+                "shasum": ""
+            },
+            "require": {
+                "ext-gd": "*"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.8.*",
+                "topthink/framework": "^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Image Package",
+            "time": "2016-09-29T06:05:43+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "f5400a12c60e513911aef41fe443fa6920952675"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/f5400a12c60e513911aef41fe443fa6920952675",
+                "reference": "f5400a12c60e513911aef41fe443fa6920952675",
+                "shasum": ""
+            },
+            "require": {
+                "composer-plugin-api": "^1.0"
+            },
+            "require-dev": {
+                "composer/composer": "1.0.*@dev"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "time": "2018-05-11T06:45:42+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.6.0",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "1.1.0"
+}

+ 2 - 0
data/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 29 - 0
docker-compose.yml

@@ -0,0 +1,29 @@
+version: "2"
+
+services:
+  mysql:
+    image: mysql:8.0.13
+    restart: always
+    command: --default-authentication-plugin=mysql_native_password
+    container_name: thinkcmf-db
+    environment:
+      - MYSQL_ROOT_PASSWORD=Root@123
+      - MYSQL_USER=thinkcmf
+      - MYSQL_PASSWORD=Thinkcmf@123
+      - MYSQL_DATABASE=thinkcmf
+      - TZ=Asia/Shanghai
+    volumes:
+      - ./mysql:/var/lib/mysql
+    ports:
+      - "3306:3306"
+      - "80:80"
+    expose:
+      - 80
+
+  thinkcmf:
+    build: .
+    network_mode: service:mysql
+    restart: always
+    container_name: thinkcmf-web
+    depends_on:
+      - mysql

+ 12 - 0
public/.htaccess

@@ -0,0 +1,12 @@
+<IfModule mod_rewrite.c>
+  Options +FollowSymlinks -Multiviews
+  RewriteEngine On
+
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^api/?(.*)$ api.php?s=$1 [QSA,PT,L]
+
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ index.php?s=$1 [QSA,PT,L]
+</IfModule>

+ 42 - 0
public/api.php

@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 老猫 <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+// [ 入口文件 ]
+
+// 调试模式开关
+define('APP_DEBUG', true);
+
+// 定义CMF根目录,可更改此目录
+define('CMF_ROOT', dirname(__DIR__) . '/');
+
+// 定义CMF数据目录,可更改此目录
+define('CMF_DATA', CMF_ROOT . 'data/');
+
+// 定义应用目录
+define('APP_PATH', CMF_ROOT . 'api/');
+
+// 定义路由目录
+define('ROUTE_PATH', APP_PATH . 'route.php');
+
+// 定义配置目录
+define('CONFIG_PATH', CMF_ROOT . 'data/config/');
+
+// 定义命名空间
+define('APP_NAMESPACE', 'api');
+
+// 定义网站入口目录
+define('WEB_ROOT', __DIR__ . '/');
+
+// 加载基础文件
+require __DIR__ . '/../vendor/thinkphp/base.php';
+
+// 执行应用并响应
+Container::get('app', [APP_PATH])->run()->send();

+ 33 - 0
public/index.php

@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 老猫 <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+// [ 入口文件 ]
+
+// 调试模式开关
+define('APP_DEBUG', true);
+
+// 定义CMF根目录,可更改此目录
+define('CMF_ROOT', dirname(__DIR__) . '/');
+
+// 定义CMF数据目录,可更改此目录
+define('CMF_DATA', CMF_ROOT . 'data/');
+
+// 定义应用目录
+define('APP_PATH', CMF_ROOT . 'app/');
+
+// 定义网站入口目录
+define('WEB_ROOT', __DIR__ . '/');
+
+// 加载基础文件
+require CMF_ROOT . 'vendor/thinkphp/base.php';
+
+// 执行应用并响应
+Container::get('app', [APP_PATH])->run()->send();

+ 90 - 0
public/plugins/qiniu/QiniuPlugin.php

@@ -0,0 +1,90 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+namespace plugins\qiniu;
+
+use cmf\lib\Plugin;
+use Qiniu\Auth;
+
+
+class QiniuPlugin extends Plugin
+{
+
+    public $info = [
+        'name'        => 'Qiniu',
+        'title'       => '七牛云存储',
+        'description' => 'ThinkCMF七牛专享优惠码:507670e8',
+        'status'      => 1,
+        'author'      => 'ThinkCMF',
+        'version'     => '1.0.1'
+    ];
+
+    public $hasAdmin = 0;//插件是否有后台管理界面
+
+    // 插件安装
+    public function install()
+    {
+        $storageOption = cmf_get_option('storage');
+        if (empty($storageOption)) {
+            $storageOption = [];
+        }
+
+        $storageOption['storages']['Qiniu'] = ['name' => '七牛云存储', 'driver' => '\\plugins\\qiniu\\lib\\Qiniu'];
+
+        cmf_set_option('storage', $storageOption);
+        return true;//安装成功返回true,失败false
+    }
+
+    // 插件卸载
+    public function uninstall()
+    {
+        $storageOption = cmf_get_option('storage');
+        if (empty($storageOption)) {
+            $storageOption = [];
+        }
+
+        unset($storageOption['storages']['Qiniu']);
+
+        cmf_set_option('storage', $storageOption);
+        return true;//卸载成功返回true,失败false
+    }
+
+    public function fetchUploadView()
+    {
+        $tab = request()->param('tab');
+
+        if ($tab == 'cloud') {
+            $config     = $this->getConfig();
+            $accessKey  = $config['accessKey'];
+            $secretKey  = $config['secretKey'];
+            $zone       = $config['zone'];
+            $uploadHost = 'upload.qiniup.com';
+            if (!empty($zone) && $zone != 'z0') {
+                $uploadHost = "upload-{$zone}.qiniup.com";
+            }
+            $auth  = new Auth($accessKey, $secretKey);
+            $token = $auth->uploadToken($config['bucket']);
+
+            $this->assign('upload_host', $uploadHost);
+            $this->assign('qiniu_up_token', $token);
+            $content = $this->fetch('upload');
+        } else {
+            $content = "has_cloud_storage";
+        }
+
+        return $content;
+    }
+
+    public function cloudStorageTab(&$param)
+    {
+
+
+
+    }
+
+}

+ 15 - 0
public/plugins/qiniu/composer.json

@@ -0,0 +1,15 @@
+{
+    "name": "thinkcmf/plugins-qiniu",
+    "description": "ThinkCMF七牛插件",
+    "type": "cmf-plugin",
+    "license": "apache2.0",
+    "authors": [
+        {
+            "name": "catman",
+            "email": "catman@thinkcmf.com"
+        }
+    ],
+    "require": {
+        "qiniu/php-sdk": "^7.2"
+    }
+}

+ 69 - 0
public/plugins/qiniu/composer.lock

@@ -0,0 +1,69 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "a327a87b5efb95afb8ab2eb7b0d7349b",
+    "packages": [
+        {
+            "name": "qiniu/php-sdk",
+            "version": "v7.2.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/qiniu/php-sdk.git",
+                "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/88d11a5857ebc6871204e9be6ceec54bf5f381e6",
+                "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Qiniu\\": "src/Qiniu"
+                },
+                "files": [
+                    "src/Qiniu/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Qiniu",
+                    "email": "sdk@qiniu.com",
+                    "homepage": "http://www.qiniu.com"
+                }
+            ],
+            "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+            "homepage": "http://developer.qiniu.com/",
+            "keywords": [
+                "cloud",
+                "qiniu",
+                "sdk",
+                "storage"
+            ],
+            "time": "2018-11-06T13:34:32+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}

+ 178 - 0
public/plugins/qiniu/config.php

@@ -0,0 +1,178 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+return [
+    'accessKey'                 => [// 在后台插件配置表单中的键名 ,会是config[text]
+        'title'   => 'AccessKey', // 表单的label标题
+        'type'    => 'text',// 表单的类型:text,password,textarea,checkbox,radio,select等
+        'value'   => '',// 表单的默认值
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => 'AccessKey不能为空'
+        ],
+        'tip'     => '<a href="https://portal.qiniu.com/signup?invitation_type=1&invitation_key=1381750588" target="_blank">马上获取</a>,充值使用ThinkCMF七牛专属优惠码<a href="http://www.thinkcmf.com/qiniu/promotion_code.html" target="_blank">507670e8</a>有更多优惠,<a href="http://www.thinkcmf.com/faq.html?url=https://www.kancloud.cn/thinkcmf/faq/507454" target="_blank">查看帮助手册</a>' //表单的帮助提示
+    ],
+    'secretKey'                 => [// 在后台插件配置表单中的键名 ,会是config[password]
+        'title'   => 'SecretKey',
+        'type'    => 'text',
+        'value'   => '',
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => 'SecretKey不能为空'
+        ],
+        'tip'     => '<a href="https://portal.qiniu.com/signup?invitation_type=1&invitation_key=1381750588" target="_blank">马上获取</a>, <a href="http://www.thinkcmf.com/faq.html?url=https://www.kancloud.cn/thinkcmf/faq/507454" target="_blank">查看帮助手册</a>'
+    ],
+    'protocol'                  => [// 在后台插件配置表单中的键名 ,会是config[select]
+        'title'   => '域名协议',
+        'type'    => 'select',
+        'options' => [//select 和radio,checkbox的子选项
+            'http'  => 'http',// 值=>显示
+            'https' => 'https',
+        ],
+        'value'   => 'http',
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => '域名协议不能为空'
+        ],
+        'tip'     => ''
+    ],
+    'domain'                    => [
+        'title'   => '空间域名',
+        'type'    => 'text',
+        'value'   => '',
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => '空间域名不能为空'
+        ],
+        'tip'     => ''
+    ],
+    'bucket'                    => [
+        'title'   => '空间名称',
+        'type'    => 'text',
+        'value'   => '',
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => '空间名称不能为空'
+        ],
+        'tip'     => ''
+    ],
+    'zone'                      => [// 在后台插件配置表单中的键名 ,会是config[select]
+        'title'   => '存储区域',
+        'type'    => 'select',
+        'options' => [//select 和radio,checkbox的子选项
+            'z0'  => '华东',// 值=>显示
+            'z1'  => '华北',
+            'z2'  => '华南',
+            'na0' => '北美',
+            'as0' => '东南亚',
+        ],
+        'value'   => 'http',
+        "rule"    => [
+            "require" => true
+        ],
+        "message" => [
+            "require" => '存储区域不能为空'
+        ],
+        'tip'     => ''
+    ],
+    'style_separator'           => [
+        'title'   => '样式分隔符',
+        'type'    => 'text',
+        'value'   => '!',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式分隔符不能为空'
+//        ],
+        'tip'     => ''
+    ],
+    'styles_watermark'          => [
+        'title'   => '样式-水印',
+        'type'    => 'explain',
+        'value'   => 'watermark',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-水印不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置 处理接口<span style="color: red;">(请注意修改水印文字)</span>:<br>imageMogr2/auto-orient/thumbnail/1080x1080>/blur/1x0/quality/75|watermark/2/text/VGhpbmtDTUY=/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGRkZGRg==/dissolve/100/gravity/SouthEast/dx/10/dy/10'
+    ],
+    'styles_avatar'             => [
+        'title'   => '样式-头像',
+        'type'    => 'explain',
+        'value'   => 'avatar',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-头像不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置 处理接口:<br>imageMogr2/auto-orient/thumbnail/!100x100r/gravity/Center/crop/100x100/quality/100/interlace/0'
+    ],
+    'styles_thumbnail120x120'   => [
+        'title'   => '样式-缩略图120x120',
+        'type'    => 'explain',
+        'value'   => 'thumbnail120x120',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-缩略图120x120不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置<br>处理接口:<br>imageMogr2/auto-orient/thumbnail/!120x120r/gravity/Center/crop/120x120/quality/100/interlace/0'
+    ],
+    'styles_thumbnail300x300'   => [
+        'title'   => '样式-缩略图300x300',
+        'type'    => 'explain',
+        'value'   => 'thumbnail300x300',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-缩略图300x300不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置<br>处理接口:<br>imageMogr2/auto-orient/thumbnail/!300x300r/gravity/Center/crop/300x300/quality/100/interlace/0'
+    ],
+    'styles_thumbnail640x640'   => [
+        'title'   => '样式-缩略图640x640',
+        'type'    => 'explain',
+        'value'   => 'thumbnail640x640',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-缩略图640x640不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置<br>处理接口:<br>imageMogr2/auto-orient/thumbnail/!640x640r/gravity/Center/crop/640x640/quality/100/interlace/0'
+    ],
+    'styles_thumbnail1080x1080' => [
+        'title'   => '样式-缩略图1080x1080',
+        'type'    => 'explain',
+        'value'   => 'thumbnail1080x1080',
+//        "rule"    => [
+//            "require" => true
+//        ],
+//        "message" => [
+//            "require" => '样式-缩略图1080x1080不能为空'
+//        ],
+        'tip'     => '请到七牛存储空间->图片样式:添加此样式名称,并进行相应设置<br>处理接口:<br>imageMogr2/auto-orient/thumbnail/!1080x1080r/gravity/Center/crop/1080x1080/quality/100/interlace/0'
+    ],
+];
+					

+ 130 - 0
public/plugins/qiniu/controller/AssetController.php

@@ -0,0 +1,130 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+namespace plugins\qiniu\controller; //Demo插件英文名,改成你的插件英文就行了
+use cmf\controller\PluginBaseController;
+use plugins\qiniu\lib\Qiniu;
+use think\Validate;
+use think\Db;
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+use Qiniu\Http\Client;
+
+class AssetController extends PluginBaseController
+{
+
+    function getUrl()
+    {
+
+        $qiniu = new Qiniu([]);
+
+        $fileHash = $this->request->param('file_hash');
+        $filname  = $this->request->param('filename');
+        $fileType = $this->request->param('filetype');
+
+        $suffix = cmf_get_file_extension($filname);
+
+        $file = $fileHash . ".{$suffix}";
+
+        $previewUrl = $fileType == 'image' ? $qiniu->getPreviewUrl($file) : $qiniu->getFileDownloadUrl($file);
+        $url        = $fileType == 'image' ? $qiniu->getImageUrl($file, 'watermark') : $qiniu->getFileDownloadUrl($file);
+
+        return $this->success('success', null, [
+            'url'         => $url,
+            'preview_url' => $previewUrl,
+            'filepath'    => $file
+        ]);
+    }
+
+    public function saveFile()
+    {
+        $userId = cmf_get_current_admin_id();
+        $userId = $userId ? $userId : cmf_get_current_user_id();
+
+        if (empty($userId)) {
+            $this->error('error');
+        }
+        $validate = new Validate([
+            'filename' => 'require',
+            'file_key' => 'require',
+        ]);
+
+        $data = $this->request->param();
+
+        $result = $validate->check($data);
+
+        if ($result !== true) {
+            $this->error($validate);
+        }
+
+        $fileKey = $data['file_key'];
+
+        $suffix = cmf_get_file_extension($data['filename']);
+
+        $config = $this->getPlugin()->getConfig();
+
+        $accessKey = $config['accessKey'];
+        $secretKey = $config['secretKey'];
+
+        $auth = new Auth($accessKey, $secretKey);
+
+
+        $client = new Client();
+
+        $encodedEntryURISrc  = \Qiniu\base64_urlSafeEncode($config['bucket'] . ':' . $fileKey);
+        $encodedEntryURIDest = \Qiniu\base64_urlSafeEncode($config['bucket'] . ':' . $fileKey . ".{$suffix}");
+
+        $signingStr    = "/move/{$encodedEntryURISrc}/{$encodedEntryURIDest}";
+        $authorization = $auth->signRequest($signingStr, '');
+
+        $url = 'http://rs.qiniu.com/' . $signingStr;
+
+        $response = $client->post($url, null, ['Authorization' => 'QBox ' . $authorization]);
+
+        if ($response->statusCode == 612) {
+            $this->error('文件不存在!');
+        }
+
+        if ($response->statusCode == 599) {
+            $this->error('文件保存失败!');
+        }
+
+        $signingStr    = "/stat/{$encodedEntryURIDest}";
+        $authorization = $auth->signRequest($signingStr, '');
+
+        $url = 'http://rs.qiniu.com/' . $signingStr;
+
+        $response = $client->get($url, ['Authorization' => 'QBox ' . $authorization]);
+
+        if ($response->statusCode != 200) {
+            $this->error('操作失败!');
+        }
+
+        $fileInfo = $response->json();
+
+        $findAsset = Db::name('asset')->where('file_key', $fileKey)->find();
+
+
+        if (empty($findAsset)) {
+
+            Db::name('asset')->insert([
+                'user_id'     => $userId,
+                'file_size'   => $fileInfo['fsize'],
+                'filename'    => $data['filename'],
+                'create_time' => time(),
+                'file_key'    => $fileKey,
+                'file_path'   => $fileKey . ".{$suffix}",
+                'suffix'      => $suffix
+            ]);
+        }
+
+        $this->success('success');
+
+    }
+
+}

+ 876 - 0
public/plugins/qiniu/error.html

@@ -0,0 +1,876 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>System Error</title>
+    <meta name="robots" content="noindex,nofollow" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+    <style>
+        /* Base */
+        body {
+            color: #333;
+            font: 14px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
+            margin: 0;
+            padding: 0 20px 20px;
+            word-break: break-word;
+        }
+        h1{
+            margin: 10px 0 0;
+            font-size: 28px;
+            font-weight: 500;
+            line-height: 32px;
+        }
+        h2{
+            color: #4288ce;
+            font-weight: 400;
+            padding: 6px 0;
+            margin: 6px 0 0;
+            font-size: 18px;
+            border-bottom: 1px solid #eee;
+        }
+        h3.subheading {
+            color: #4288ce;
+            margin: 6px 0 0;
+            font-weight: 400;
+        }
+        h3{
+            margin: 12px;
+            font-size: 16px;
+            font-weight: bold;
+        }
+        abbr{
+            cursor: help;
+            text-decoration: underline;
+            text-decoration-style: dotted;
+        }
+        a{
+            color: #868686;
+            cursor: pointer;
+        }
+        a:hover{
+            text-decoration: underline;
+        }
+        .line-error{
+            background: #f8cbcb;
+        }
+
+        .echo table {
+            width: 100%;
+        }
+
+        .echo pre {
+            padding: 16px;
+            overflow: auto;
+            font-size: 85%;
+            line-height: 1.45;
+            background-color: #f7f7f7;
+            border: 0;
+            border-radius: 3px;
+            font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+        }
+
+        .echo pre > pre {
+            padding: 0;
+            margin: 0;
+        }
+        /* Layout */
+        .col-md-3 {
+            width: 25%;
+        }
+        .col-md-9 {
+            width: 75%;
+        }
+        [class^="col-md-"] {
+            float: left;
+        }
+        .clearfix {
+            clear:both;
+        }
+        @media only screen
+        and (min-device-width : 375px)
+        and (max-device-width : 667px) {
+            .col-md-3,
+            .col-md-9 {
+                width: 100%;
+            }
+        }
+        /* Exception Info */
+        .exception {
+            margin-top: 20px;
+        }
+        .exception .message{
+            padding: 12px;
+            border: 1px solid #ddd;
+            border-bottom: 0 none;
+            line-height: 18px;
+            font-size:16px;
+            border-top-left-radius: 4px;
+            border-top-right-radius: 4px;
+            font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑";
+        }
+
+        .exception .code{
+            float: left;
+            text-align: center;
+            color: #fff;
+            margin-right: 12px;
+            padding: 16px;
+            border-radius: 4px;
+            background: #999;
+        }
+        .exception .source-code{
+            padding: 6px;
+            border: 1px solid #ddd;
+
+            background: #f9f9f9;
+            overflow-x: auto;
+
+        }
+        .exception .source-code pre{
+            margin: 0;
+        }
+        .exception .source-code pre ol{
+            margin: 0;
+            color: #4288ce;
+            display: inline-block;
+            min-width: 100%;
+            box-sizing: border-box;
+            font-size:14px;
+            font-family: "Century Gothic",Consolas,"Liberation Mono",Courier,Verdana;
+            padding-left: 48px;
+        }
+        .exception .source-code pre li{
+            border-left: 1px solid #ddd;
+            height: 18px;
+            line-height: 18px;
+        }
+        .exception .source-code pre code{
+            color: #333;
+            height: 100%;
+            display: inline-block;
+            border-left: 1px solid #fff;
+            font-size:14px;
+            font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑";
+        }
+        .exception .trace{
+            padding: 6px;
+            border: 1px solid #ddd;
+            border-top: 0 none;
+            line-height: 16px;
+            font-size:14px;
+            font-family: Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑";
+        }
+        .exception .trace ol{
+            margin: 12px;
+        }
+        .exception .trace ol li{
+            padding: 2px 4px;
+        }
+        .exception div:last-child{
+            border-bottom-left-radius: 4px;
+            border-bottom-right-radius: 4px;
+        }
+
+        /* Exception Variables */
+        .exception-var table{
+            width: 100%;
+            margin: 12px 0;
+            box-sizing: border-box;
+            table-layout:fixed;
+            word-wrap:break-word;
+        }
+        .exception-var table caption{
+            text-align: left;
+            font-size: 16px;
+            font-weight: bold;
+            padding: 6px 0;
+        }
+        .exception-var table caption small{
+            font-weight: 300;
+            display: inline-block;
+            margin-left: 10px;
+            color: #ccc;
+        }
+        .exception-var table tbody{
+            font-size: 13px;
+            font-family: Consolas,"Liberation Mono",Courier,"微软雅黑";
+        }
+        .exception-var table td{
+            padding: 0 6px;
+            vertical-align: top;
+            word-break: break-all;
+        }
+        .exception-var table td:first-child{
+            width: 28%;
+            font-weight: bold;
+            white-space: nowrap;
+        }
+        .exception-var table td pre{
+            margin: 0;
+        }
+
+        /* Copyright Info */
+        .copyright{
+            margin-top: 24px;
+            padding: 12px 0;
+            border-top: 1px solid #eee;
+        }
+
+        /* SPAN elements with the classes below are added by prettyprint. */
+        pre.prettyprint .pln { color: #000 }  /* plain text */
+        pre.prettyprint .str { color: #080 }  /* string content */
+        pre.prettyprint .kwd { color: #008 }  /* a keyword */
+        pre.prettyprint .com { color: #800 }  /* a comment */
+        pre.prettyprint .typ { color: #606 }  /* a type name */
+        pre.prettyprint .lit { color: #066 }  /* a literal value */
+        /* punctuation, lisp open bracket, lisp close bracket */
+        pre.prettyprint .pun, pre.prettyprint .opn, pre.prettyprint .clo { color: #660 }
+        pre.prettyprint .tag { color: #008 }  /* a markup tag name */
+        pre.prettyprint .atn { color: #606 }  /* a markup attribute name */
+        pre.prettyprint .atv { color: #080 }  /* a markup attribute value */
+        pre.prettyprint .dec, pre.prettyprint .var { color: #606 }  /* a declaration; a variable name */
+        pre.prettyprint .fun { color: red }  /* a function name */
+    </style>
+</head>
+<body>
+<div class="echo">
+</div>
+<div class="exception">
+    <div class="message">
+
+        <div class="info">
+            <div>
+                <h2>[8]&nbsp;<abbr title="think\exception\ErrorException">ErrorException</abbr> in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Config.php line 45">Config.php line 45</a></h2>
+            </div>
+            <div><h1>Trying to get property of non-object</h1></div>
+        </div>
+
+    </div>
+    <div class="source-code">
+            <pre class="prettyprint lang-php"><ol start="36"><li class="line-36"><code>    public function getUpHost($accessKey, $bucket)
+</code></li><li class="line-37"><code>    {
+</code></li><li class="line-38"><code>        $zone = $this-&gt;getZone($accessKey, $bucket);
+</code></li><li class="line-39"><code>        if ($this-&gt;useHTTPS === true) {
+</code></li><li class="line-40"><code>            $scheme = &quot;https://&quot;;
+</code></li><li class="line-41"><code>        } else {
+</code></li><li class="line-42"><code>            $scheme = &quot;http://&quot;;
+</code></li><li class="line-43"><code>        }
+</code></li><li class="line-44"><code>
+</code></li><li class="line-45"><code>        $host = $zone-&gt;srcUpHosts[0];
+</code></li><li class="line-46"><code>        if ($this-&gt;useCdnDomains === true) {
+</code></li><li class="line-47"><code>            $host = $zone-&gt;cdnUpHosts[0];
+</code></li><li class="line-48"><code>        }
+</code></li><li class="line-49"><code>
+</code></li><li class="line-50"><code>        return $scheme . $host;
+</code></li><li class="line-51"><code>    }
+</code></li><li class="line-52"><code>
+</code></li><li class="line-53"><code>    public function getUpBackupHost($accessKey, $bucket)
+</code></li><li class="line-54"><code>    {
+</code></li></ol></pre>
+    </div>
+    <div class="trace">
+        <h2>Call Stack</h2>
+        <ol>
+            <li>in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Config.php line 45">Config.php line 45</a></li>
+            <li>
+                at <abbr title="think\Error">Error</abbr>::appError(8, '<a class="toggle" title="Trying to get property of non-object">Trying to get proper...</a>', '<a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Config.php">/Users/Dean/git/thin...</a>', 45, ['accessKey' => '<a class="toggle" title="XdiS6FfUnX2ev_FbtjEiJX2R0gS_JzfiabXpCOLW">XdiS6FfUnX2ev_FbtjEi...</a>', 'bucket' => 'thinkcmf-portal', 'zone' => [<em>null</em>, <em>object</em>(<abbr title="Qiniu\Http\Error">Error</abbr>)], ...]) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Config.php line 45">Config.php line 45</a>                </li>
+            <li>
+                at <abbr title="Qiniu\Config">Config</abbr>->getUpHost('<a class="toggle" title="XdiS6FfUnX2ev_FbtjEiJX2R0gS_JzfiabXpCOLW">XdiS6FfUnX2ev_FbtjEi...</a>', 'thinkcmf-portal') in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php line 58">FormUploader.php line 58</a>                </li>
+            <li>
+                at <abbr title="Qiniu\Storage\FormUploader">FormUploader</abbr>::put('<a class="toggle" title="XdiS6FfUnX2ev_FbtjEiJX2R0gS_JzfiabXpCOLW:nJAfDr3ACxxT1W_GHdblcddZPBs=:eyJzY29wZSI6InRoaW5rY21mLXBvcnRhbCIsImRlYWRsaW5lIjoxNTUxNjExMjM4fQ==">XdiS6FfUnX2ev_FbtjEi...</a>', '<a class="toggle" title="portal/20190303/59adc8a781b1be671defa87092249151.jpg">portal/20190303/59ad...</a>', '<a class="toggle" title="">...</a>', <em>object</em>(<abbr title="Qiniu\Config">Config</abbr>), <em>null</em>, '<a class="toggle" title="application/octet-stream">application/octet-st...</a>', '<a class="toggle" title="59adc8a781b1be671defa87092249151.jpg">59adc8a781b1be671def...</a>') in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php line 108">UploadManager.php line 108</a>                </li>
+            <li>
+                at <abbr title="Qiniu\Storage\UploadManager">UploadManager</abbr>->putFile('<a class="toggle" title="XdiS6FfUnX2ev_FbtjEiJX2R0gS_JzfiabXpCOLW:nJAfDr3ACxxT1W_GHdblcddZPBs=:eyJzY29wZSI6InRoaW5rY21mLXBvcnRhbCIsImRlYWRsaW5lIjoxNTUxNjExMjM4fQ==">XdiS6FfUnX2ev_FbtjEi...</a>', '<a class="toggle" title="portal/20190303/59adc8a781b1be671defa87092249151.jpg">portal/20190303/59ad...</a>', '<a class="toggle" title="./upload/portal/20190303/59adc8a781b1be671defa87092249151.jpg">./upload/portal/2019...</a>') in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/plugins/qiniu/lib/Qiniu.php line 51">Qiniu.php line 51</a>                </li>
+            <li>
+                at <abbr title="plugins\qiniu\lib\Qiniu">Qiniu</abbr>->upload('<a class="toggle" title="portal/20190303/59adc8a781b1be671defa87092249151.jpg">portal/20190303/59ad...</a>', '<a class="toggle" title="./upload/portal/20190303/59adc8a781b1be671defa87092249151.jpg">./upload/portal/2019...</a>', 'image', <em>null</em>) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/cmf/lib/Storage.php line 70">Storage.php line 70</a>                </li>
+            <li>
+                at <abbr title="cmf\lib\Storage">Storage</abbr>->upload('<a class="toggle" title="portal/20190303/59adc8a781b1be671defa87092249151.jpg">portal/20190303/59ad...</a>', '<a class="toggle" title="./upload/portal/20190303/59adc8a781b1be671defa87092249151.jpg">./upload/portal/2019...</a>', 'image') in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/cmf/lib/Upload.php line 327">Upload.php line 327</a>                </li>
+            <li>
+                at <abbr title="cmf\lib\Upload">Upload</abbr>->upload() in <a class="toggle" title="/Users/Dean/git/thinkcmf5/app/user/controller/AssetController.php line 42">AssetController.php line 42</a>                </li>
+            <li>
+                at <abbr title="app\user\controller\AssetController">AssetController</abbr>->webuploader()                </li>
+            <li>
+                at <abbr title="ReflectionMethod">ReflectionMethod</abbr>->invokeArgs(<em>object</em>(<abbr title="app\user\controller\AssetController">AssetController</abbr>), []) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/think/App.php line 343">App.php line 343</a>                </li>
+            <li>
+                at <abbr title="think\App">App</abbr>::invokeMethod([<em>object</em>(<abbr title="app\user\controller\AssetController">AssetController</abbr>), 'webuploader'], []) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/think/App.php line 611">App.php line 611</a>                </li>
+            <li>
+                at <abbr title="think\App">App</abbr>::module(['user', 'asset', 'webuploader'], ['app_host' => '', 'app_debug' => <em>true</em>, 'app_trace' => <em>true</em>, ...], <em>true</em>) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/think/App.php line 456">App.php line 456</a>                </li>
+            <li>
+                at <abbr title="think\App">App</abbr>::exec(['type' => 'module', 'module' => ['user', 'asset', 'webuploader']], ['app_host' => '', 'app_debug' => <em>true</em>, 'app_trace' => <em>true</em>, ...]) in <a class="toggle" title="/Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/think/App.php line 139">App.php line 139</a>                </li>
+            <li>
+                at <abbr title="think\App">App</abbr>::run() in <a class="toggle" title="/Users/Dean/git/thinkcmf5/public/index.php line 44">index.php line 44</a>                </li>
+        </ol>
+    </div>
+</div>
+
+
+<div class="exception-var">
+    <h2>Environment Variables</h2>
+    <div>
+        <h3 class="subheading">GET Data</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>_ajax</strong></div>
+                <div class="col-md-9"><small>
+                    1                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">POST Data</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>app</strong></div>
+                <div class="col-md-9"><small>
+                    portal                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>filetype</strong></div>
+                <div class="col-md-9"><small>
+                    image                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>id</strong></div>
+                <div class="col-md-9"><small>
+                    WU_FILE_0                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>name</strong></div>
+                <div class="col-md-9"><small>
+                    封面.001.jpg                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>type</strong></div>
+                <div class="col-md-9"><small>
+                    image/jpeg                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>lastModifiedDate</strong></div>
+                <div class="col-md-9"><small>
+                    Sat Mar 24 2018 23:11:37 GMT+0800 (China Standard Time)                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>size</strong></div>
+                <div class="col-md-9"><small>
+                    956061                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">Files</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>file</strong></div>
+                <div class="col-md-9"><small>
+                    {
+                    &quot;name&quot;: &quot;\u5c01\u9762.001.jpg&quot;,
+                    &quot;type&quot;: &quot;image\/jpeg&quot;,
+                    &quot;tmp_name&quot;: &quot;\/private\/var\/tmp\/phpTaT6hj&quot;,
+                    &quot;error&quot;: 0,
+                    &quot;size&quot;: 956061
+                    }                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">Cookies</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>OUTFOX_SEARCH_USER_ID_NCOO</strong></div>
+                <div class="col-md-9"><small>
+                    1411363915.8599343                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>admin_username</strong></div>
+                <div class="col-md-9"><small>
+                    admin                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>PHPSESSID</strong></div>
+                <div class="col-md-9"><small>
+                    urr95c6rt20552hako7jsrfaiu                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>thinkphp_show_page_trace</strong></div>
+                <div class="col-md-9"><small>
+                    0|1                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">Session</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>think</strong></div>
+                <div class="col-md-9"><small>
+                    {
+                    &quot;__SP_ADMIN_LOGIN_PAGE_SHOWED_SUCCESS__&quot;: true,
+                    &quot;ADMIN_ID&quot;: 1,
+                    &quot;name&quot;: &quot;admin&quot;,
+                    &quot;token&quot;: &quot;2eb4d69a7c70f034a080f195d1e8625a25c216178d353d36d569bd14c0078001&quot;,
+                    &quot;cmf_default_theme&quot;: &quot;simpleboot3&quot;
+                    }                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">Server/Request Data</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REDIRECT_STATUS</strong></div>
+                <div class="col-md-9"><small>
+                    200                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_HOST</strong></div>
+                <div class="col-md-9"><small>
+                    cmf5.im                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_CONNECTION</strong></div>
+                <div class="col-md-9"><small>
+                    keep-alive                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONTENT_LENGTH</strong></div>
+                <div class="col-md-9"><small>
+                    957004                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_PRAGMA</strong></div>
+                <div class="col-md-9"><small>
+                    no-cache                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_CACHE_CONTROL</strong></div>
+                <div class="col-md-9"><small>
+                    no-cache                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_ORIGIN</strong></div>
+                <div class="col-md-9"><small>
+                    http://cmf5.im                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_USER_AGENT</strong></div>
+                <div class="col-md-9"><small>
+                    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONTENT_TYPE</strong></div>
+                <div class="col-md-9"><small>
+                    multipart/form-data; boundary=----WebKitFormBoundaryYZzPGMjLHBfHZtKA                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_ACCEPT</strong></div>
+                <div class="col-md-9"><small>
+                    */*                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_REFERER</strong></div>
+                <div class="col-md-9"><small>
+                    http://cmf5.im/user/Asset/webuploader?&amp;multi=0&amp;filetype=image&amp;app=portal                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_ACCEPT_ENCODING</strong></div>
+                <div class="col-md-9"><small>
+                    gzip, deflate                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_ACCEPT_LANGUAGE</strong></div>
+                <div class="col-md-9"><small>
+                    en,zh-CN;q=0.9,zh;q=0.8,la;q=0.7                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTTP_COOKIE</strong></div>
+                <div class="col-md-9"><small>
+                    OUTFOX_SEARCH_USER_ID_NCOO=1411363915.8599343; admin_username=admin; PHPSESSID=urr95c6rt20552hako7jsrfaiu; thinkphp_show_page_trace=0|1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /usr/bin:/bin:/usr/sbin:/sbin                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_SIGNATURE</strong></div>
+                <div class="col-md-9"><small>
+                </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_SOFTWARE</strong></div>
+                <div class="col-md-9"><small>
+                    Apache/2.4.35 (Unix) PHP/7.1.1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_NAME</strong></div>
+                <div class="col-md-9"><small>
+                    cmf5.im                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_ADDR</strong></div>
+                <div class="col-md-9"><small>
+                    127.0.0.1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_PORT</strong></div>
+                <div class="col-md-9"><small>
+                    80                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REMOTE_ADDR</strong></div>
+                <div class="col-md-9"><small>
+                    127.0.0.1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>DOCUMENT_ROOT</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REQUEST_SCHEME</strong></div>
+                <div class="col-md-9"><small>
+                    http                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONTEXT_PREFIX</strong></div>
+                <div class="col-md-9"><small>
+                </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONTEXT_DOCUMENT_ROOT</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_ADMIN</strong></div>
+                <div class="col-md-9"><small>
+                    webmaster@dummy-host2.example.com                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SCRIPT_FILENAME</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/index.php                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REMOTE_PORT</strong></div>
+                <div class="col-md-9"><small>
+                    58049                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REDIRECT_URL</strong></div>
+                <div class="col-md-9"><small>
+                    /user/asset/webuploader.html                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REDIRECT_QUERY_STRING</strong></div>
+                <div class="col-md-9"><small>
+                    s=user/asset/webuploader.html&amp;_ajax=1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>GATEWAY_INTERFACE</strong></div>
+                <div class="col-md-9"><small>
+                    CGI/1.1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SERVER_PROTOCOL</strong></div>
+                <div class="col-md-9"><small>
+                    HTTP/1.1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REQUEST_METHOD</strong></div>
+                <div class="col-md-9"><small>
+                    POST                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>QUERY_STRING</strong></div>
+                <div class="col-md-9"><small>
+                    s=user/asset/webuploader.html&amp;_ajax=1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REQUEST_URI</strong></div>
+                <div class="col-md-9"><small>
+                    /user/asset/webuploader.html?_ajax=1                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>SCRIPT_NAME</strong></div>
+                <div class="col-md-9"><small>
+                    /index.php                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>PHP_SELF</strong></div>
+                <div class="col-md-9"><small>
+                    /index.php                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REQUEST_TIME_FLOAT</strong></div>
+                <div class="col-md-9"><small>
+                    1551607638.106                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>REQUEST_TIME</strong></div>
+                <div class="col-md-9"><small>
+                    1551607638                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>PATH_INFO</strong></div>
+                <div class="col-md-9"><small>
+                    user/asset/webuploader.html                    </small></div>
+            </div>
+        </div>
+    </div>
+    <div>
+        <div class="clearfix">
+            <div class="col-md-3"><strong>Environment Variables</strong></div>
+            <div class="col-md-9"><small>empty</small></div>
+        </div>
+    </div>
+    <div>
+        <h3 class="subheading">ThinkPHP Constants</h3>
+        <div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>APP_DEBUG</strong></div>
+                <div class="col-md-9"><small>
+                    true                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CMF_ROOT</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>APP_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../app/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CMF_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../simplewind/cmf/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>WEB_ROOT</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>PLUGINS_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/plugins/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>EXTEND_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../simplewind/extend/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>VENDOR_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../simplewind/vendor/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>RUNTIME_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../data/runtime/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>THINKCMF_VERSION</strong></div>
+                <div class="col-md-9"><small>
+                    5.0.190111                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>THINK_VERSION</strong></div>
+                <div class="col-md-9"><small>
+                    5.0.24                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>THINK_START_TIME</strong></div>
+                <div class="col-md-9"><small>
+                    1551607638.1396                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>THINK_START_MEM</strong></div>
+                <div class="col-md-9"><small>
+                    395672                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>EXT</strong></div>
+                <div class="col-md-9"><small>
+                    .php                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>DS</strong></div>
+                <div class="col-md-9"><small>
+                    /                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>THINK_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/simplewind/thinkphp/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>LIB_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CORE_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/think/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>TRAIT_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/simplewind/thinkphp/library/traits/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>ROOT_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>LOG_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../data/runtime/log/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CACHE_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../data/runtime/cache/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>TEMP_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../data/runtime/temp/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONF_PATH</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/public/../app/                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>CONF_EXT</strong></div>
+                <div class="col-md-9"><small>
+                    .php                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>ENV_PREFIX</strong></div>
+                <div class="col-md-9"><small>
+                    PHP_                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>IS_CLI</strong></div>
+                <div class="col-md-9"><small>
+                    false                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>IS_WIN</strong></div>
+                <div class="col-md-9"><small>
+                    false                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>HTMLPURIFIER_PREFIX</strong></div>
+                <div class="col-md-9"><small>
+                    /Users/Dean/git/thinkcmf5/simplewind/vendor/ezyang/htmlpurifier/library                    </small></div>
+            </div>
+            <div class="clearfix">
+                <div class="col-md-3"><strong>QINIU_FUNCTIONS_VERSION</strong></div>
+                <div class="col-md-9"><small>
+                    7.2.7                    </small></div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="copyright">
+    <a title="官方网站" href="http://www.thinkphp.cn">ThinkPHP</a>
+    <span>V5.0.24</span>
+    <span>{ 十年磨一剑-为API开发设计的高性能框架 }</span>
+</div>
+<script>
+    var LINE = 45;
+
+    function $(selector, node){
+        var elements;
+
+        node = node || document;
+        if(document.querySelectorAll){
+            elements = node.querySelectorAll(selector);
+        } else {
+            switch(selector.substr(0, 1)){
+                case '#':
+                    elements = [node.getElementById(selector.substr(1))];
+                    break;
+                case '.':
+                    if(document.getElementsByClassName){
+                        elements = node.getElementsByClassName(selector.substr(1));
+                    } else {
+                        elements = get_elements_by_class(selector.substr(1), node);
+                    }
+                    break;
+                default:
+                    elements = node.getElementsByTagName();
+            }
+        }
+        return elements;
+
+        function get_elements_by_class(search_class, node, tag) {
+            var elements = [], eles,
+                pattern  = new RegExp('(^|\\s)' + search_class + '(\\s|$)');
+
+            node = node || document;
+            tag  = tag  || '*';
+
+            eles = node.getElementsByTagName(tag);
+            for(var i = 0; i < eles.length; i++) {
+                if(pattern.test(eles[i].className)) {
+                    elements.push(eles[i])
+                }
+            }
+
+            return elements;
+        }
+    }
+
+    $.getScript = function(src, func){
+        var script = document.createElement('script');
+
+        script.async  = 'async';
+        script.src    = src;
+        script.onload = func || function(){};
+
+        $('head')[0].appendChild(script);
+    }
+
+    ;(function(){
+        var files = $('.toggle');
+        var ol    = $('ol', $('.prettyprint')[0]);
+        var li    = $('li', ol[0]);
+
+        // 短路径和长路径变换
+        for(var i = 0; i < files.length; i++){
+            files[i].ondblclick = function(){
+                var title = this.title;
+
+                this.title = this.innerHTML;
+                this.innerHTML = title;
+            }
+        }
+
+        // 设置出错行
+        var err_line = $('.line-' + LINE, ol[0])[0];
+        err_line.className = err_line.className + ' line-error';
+
+        $.getScript('//cdn.bootcss.com/prettify/r298/prettify.min.js', function(){
+            prettyPrint();
+
+            // 解决Firefox浏览器一个很诡异的问题
+            // 当代码高亮后,ol的行号莫名其妙的错位
+            // 但是只要刷新li里面的html重新渲染就没有问题了
+            if(window.navigator.userAgent.indexOf('Firefox') >= 0){
+                ol[0].innerHTML = ol[0].innerHTML;
+            }
+        });
+
+    })();
+</script>
+</body>
+</html>

+ 166 - 0
public/plugins/qiniu/lib/Qiniu.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace plugins\qiniu\lib;
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+class Qiniu
+{
+
+    private $config;
+
+    private $storageRoot;
+
+    /**
+     * @var \plugins\qiniu\QiniuPlugin
+     */
+    private $plugin;
+
+    /**
+     * Qiniu constructor.
+     * @param $config
+     */
+    public function __construct($config)
+    {
+        $pluginClass = cmf_get_plugin_class('Qiniu');
+
+        $this->plugin = new $pluginClass();
+        $this->config = $this->plugin->getConfig();
+
+        $this->storageRoot = $this->config['protocol'] . '://' . $this->config['domain'] . '/';
+    }
+
+    /**
+     * 文件上传
+     * @param string $file     上传文件路径
+     * @param string $filePath 文件路径相对于upload目录
+     * @param string $fileType 文件类型,image,video,audio,file
+     * @param array  $param    额外参数
+     * @return mixed
+     */
+    public function upload($file, $filePath, $fileType = 'image', $param = null)
+    {
+        $accessKey = $this->config['accessKey'];
+        $secretKey = $this->config['secretKey'];
+        $watermark = empty($this->config['styles_watermark']) ? 'watermark' : $this->config['styles_watermark'];
+        $upManager = new UploadManager();
+        $auth      = new Auth($accessKey, $secretKey);
+        $token     = $auth->uploadToken($this->config['bucket']);
+
+        $result = $upManager->putFile($token, $file, $filePath);
+
+        $previewUrl = $fileType == 'image' ? $this->getPreviewUrl($file, $watermark) : $this->getFileDownloadUrl($file);
+        $url        = $fileType == 'image' ? $this->getImageUrl($file, $watermark) : $this->getFileDownloadUrl($file);
+
+        return [
+            'preview_url' => $previewUrl,
+            'url'         => $url,
+        ];
+    }
+
+    /**
+     * 获取图片预览地址
+     * @param string $file
+     * @param string $style
+     * @return mixed
+     */
+    public function getPreviewUrl($file, $style = 'watermark')
+    {
+        $url = $this->getUrl($file, $style);
+
+        return $url;
+    }
+
+    /**
+     * 获取图片地址
+     * @param string $file
+     * @param string $style
+     * @return mixed
+     */
+    public function getImageUrl($file, $style = 'watermark')
+    {
+        $config = $this->config;
+        $url    = $this->storageRoot . $file;
+
+        if (!empty($style)) {
+            $url = $url . $config['style_separator'] . $style;
+        }
+
+        return $url;
+    }
+
+    /**
+     * 获取文件地址
+     * @param string $file
+     * @param string $style
+     * @return mixed
+     */
+    public function getUrl($file, $style = '')
+    {
+        $config = $this->config;
+        $url    = $this->storageRoot . $file;
+
+        if (!empty($style)) {
+            $url = $url . $config['style_separator'] . $style;
+        }
+
+        return $url;
+    }
+
+    /**
+     * 获取文件下载地址
+     * @param string $file
+     * @param int    $expires
+     * @return mixed
+     */
+    public function getFileDownloadUrl($file, $expires = 3600)
+    {
+        $accessKey = $this->config['accessKey'];
+        $secretKey = $this->config['secretKey'];
+        $auth      = new Auth($accessKey, $secretKey);
+        $url       = $this->getUrl($file);
+        $filename  = db('asset')->where('file_path', $file)->value('filename');
+
+        $url = $auth->privateDownloadUrl($url, $expires);
+
+        if (!empty($filename)) {
+            $url .= '&attname=' . urlencode($filename);
+        }
+        return $url;
+    }
+
+    /**
+     * 获取云存储域名
+     * @return mixed
+     */
+    public function getDomain()
+    {
+        return $this->config['domain'];
+    }
+
+    /**
+     * 获取文件相对上传目录路径
+     * @param string $url
+     * @return mixed
+     */
+    public function getFilePath($url)
+    {
+        $parsedUrl = parse_url($url);
+
+        if (!empty($parsedUrl['path'])) {
+            $url            = ltrim($parsedUrl['path'], '/\\');
+            $config         = $this->config;
+            $styleSeparator = $config['style_separator'];
+
+            $styleSeparatorPosition = strpos($url, $styleSeparator);
+            if ($styleSeparatorPosition !== false) {
+                $url = substr($url, 0, strpos($url, $styleSeparator));
+            }
+        } else {
+            $url = '';
+        }
+
+        return $url;
+    }
+}

+ 7 - 0
public/plugins/qiniu/vendor/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit30357131f9fa9d17043f45e2ab1352c2::getLoader();

+ 445 - 0
public/plugins/qiniu/vendor/composer/ClassLoader.php

@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}

+ 21 - 0
public/plugins/qiniu/vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 9 - 0
public/plugins/qiniu/vendor/composer/autoload_classmap.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 10 - 0
public/plugins/qiniu/vendor/composer/autoload_files.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '841780ea2e1d6545ea3a253239d59c05' => $vendorDir . '/qiniu/php-sdk/src/Qiniu/functions.php',
+);

+ 9 - 0
public/plugins/qiniu/vendor/composer/autoload_namespaces.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 10 - 0
public/plugins/qiniu/vendor/composer/autoload_psr4.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
+);

+ 70 - 0
public/plugins/qiniu/vendor/composer/autoload_real.php

@@ -0,0 +1,70 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit30357131f9fa9d17043f45e2ab1352c2
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInit30357131f9fa9d17043f45e2ab1352c2', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInit30357131f9fa9d17043f45e2ab1352c2', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInit30357131f9fa9d17043f45e2ab1352c2::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInit30357131f9fa9d17043f45e2ab1352c2::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequire30357131f9fa9d17043f45e2ab1352c2($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequire30357131f9fa9d17043f45e2ab1352c2($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 35 - 0
public/plugins/qiniu/vendor/composer/autoload_static.php

@@ -0,0 +1,35 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit30357131f9fa9d17043f45e2ab1352c2
+{
+    public static $files = array (
+        '841780ea2e1d6545ea3a253239d59c05' => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/functions.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'Q' => 
+        array (
+            'Qiniu\\' => 6,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Qiniu\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu',
+        ),
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit30357131f9fa9d17043f45e2ab1352c2::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit30357131f9fa9d17043f45e2ab1352c2::$prefixDirsPsr4;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 55 - 0
public/plugins/qiniu/vendor/composer/installed.json

@@ -0,0 +1,55 @@
+[
+    {
+        "name": "qiniu/php-sdk",
+        "version": "v7.2.7",
+        "version_normalized": "7.2.7.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/qiniu/php-sdk.git",
+            "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/88d11a5857ebc6871204e9be6ceec54bf5f381e6",
+            "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "~4.0",
+            "squizlabs/php_codesniffer": "~2.3"
+        },
+        "time": "2018-11-06T13:34:32+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Qiniu\\": "src/Qiniu"
+            },
+            "files": [
+                "src/Qiniu/functions.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Qiniu",
+                "email": "sdk@qiniu.com",
+                "homepage": "http://www.qiniu.com"
+            }
+        ],
+        "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+        "homepage": "http://developer.qiniu.com/",
+        "keywords": [
+            "cloud",
+            "qiniu",
+            "sdk",
+            "storage"
+        ]
+    }
+]

+ 12 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/.gitignore

@@ -0,0 +1,12 @@
+*.phar
+*.zip
+build/artifacts
+phpunit.xml
+phpunit.functional.xml
+.DS_Store
+.swp
+.build
+composer.lock
+vendor
+src/package.xml
+.idea/

+ 35 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/.scrutinizer.yml

@@ -0,0 +1,35 @@
+filter:
+    excluded_paths: [tests/*]
+checks:
+    php:
+        code_rating: true
+        remove_extra_empty_lines: true
+        remove_php_closing_tag: true
+        remove_trailing_whitespace: true
+        fix_use_statements:
+            remove_unused: true
+            preserve_multiple: false
+            preserve_blanklines: true
+            order_alphabetically: true
+        fix_php_opening_tag: true
+        fix_linefeed: true
+        fix_line_ending: true
+        fix_identation_4spaces: true
+        fix_doc_comments: true
+tools:
+    external_code_coverage:
+        timeout: 1200
+        runs: 3
+    php_analyzer: true
+    php_code_coverage: false
+    php_code_sniffer:
+        config:
+            standard: PSR2
+        filter:
+            paths: ['src']
+    php_loc:
+        enabled: true
+        excluded_dirs: [vendor, tests]
+    php_cpd:
+        enabled: true
+        excluded_dirs: [vendor, tests]

+ 23 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/.travis.yml

@@ -0,0 +1,23 @@
+sudo: false
+language: php
+
+php:
+  - 5.4
+  - 5.5
+  - 5.6
+  - 7.0
+
+before_script:
+  - export QINIU_TEST_ENV="travis"
+  - travis_retry composer self-update
+  - travis_retry composer install --no-interaction --prefer-source --dev
+
+script:
+  - ./vendor/bin/phpcs --standard=PSR2 src
+  - ./vendor/bin/phpcs --standard=PSR2 examples
+  - ./vendor/bin/phpcs --standard=PSR2 tests
+  - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover tests/Qiniu/Tests/
+
+after_script:
+  - wget https://scrutinizer-ci.com/ocular.phar
+  - php ocular.phar code-coverage:upload --format=php-clover coverage.clover

+ 105 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/CHANGELOG.md

@@ -0,0 +1,105 @@
+# Changelog
+
+## 7.2.7 (2018-11-06)
+* 添加 QVM 内网上传到 KODO 的 zone 设置
+
+## 7.2.6 (2018-05-18)
+* 修复rs,rsf在不同机房默认的https域名
+
+## 7.2.5 (2018-05-10)
+* 修复表单上传中多余的参数checkCrc导致的fname错位问题
+
+## 7.2.4 (2018-05-09)
+### 增加
+* 连麦功能
+
+## 7.2.3 (2018-01-20)
+### 增加
+* 新加坡机房
+### 修正
+* 获取域名的入口域名
+* http回复头部兼容大小写
+
+## 7.2.2 (2017-11-06)
+### 增加
+* Qiniu算法的鉴权方法
+
+## 7.1.4 (2017-06-21)
+### 增加
+* cdn 文件/目录 刷新
+* cdn 获取 流量/带宽 
+* cdn 获取域名的访问日志列表
+* cdn 对资源链接进行时间戳防盗链签名
+
+## 7.1.3 (2016-11-18)
+### 增加
+* move, copy操作增加force参数
+
+## 7.1.2 (2016-11-12)
+### 修正
+* 明确抛出获取各区域域名失败时的报错
+
+## 7.1.1 (2016-11-02)
+### 修正
+* 多区域配置文件存储目录从home修改到tmp目录
+
+
+## 7.1.0 (2016-10-22)
+### 增加
+* 多存储区域的支持
+
+## 7.0.8 (2016-07-19)
+### 增加
+* demo
+* https url 支持
+* deleteAfterDays 策略
+* 添加图片处理链接统一拼接方法 by @SherlockRen
+
+## 7.0.7 (2016-01-12)
+### 修正
+* PersistentFop参数pipeline和notify_url失效
+* resume 模式 close file inputstream
+
+## 7.0.6 (2015-12-05)
+### 修正
+* php7.0 Json 对空字符串解析单元测试报错
+* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages
+* fetch 支持不指定key, by @sinkcup
+
+## 7.0.5 (2015-10-29)
+### 增加
+* 增加上传策略最小文件大小限制 fsizeMin
+* 增加常见examples
+
+## 7.0.4 (2015-07-23)
+### 修正
+* 一些地方的严格比较检查
+* resumeupload 备用地址失效
+
+## 7.0.3 (2015-07-10)
+### 修改
+* 多zone 支持
+
+## 7.0.2 (2015-04-18)
+### 修改
+* fetch 接口返回内容调整
+* pfop 接口调整
+
+###修正
+* exception 类调用
+
+## 7.0.1 (2015-03-27)
+### 增加
+* 增加代码注释
+
+## 7.0.0 (2015-02-03)
+
+### 增加
+* 简化上传接口
+* 自动选择断点续上传还是直传
+* 重构代码,接口和内部结构更清晰
+* 改变mime
+* 代码覆盖度报告
+* policy改为array, 便于灵活增加,并加入过期字段检查
+* 文件列表支持目录形式
+* 利用元编程方式支持 fop 和 pfop

+ 30 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/CONTRIBUTING.md

@@ -0,0 +1,30 @@
+# 贡献代码指南
+
+我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。
+
+一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。
+
+## Pull Request要求
+
+- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。
+
+- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2  进行格式化。
+
+- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。
+
+- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。
+
+- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。
+
+- **创建feature分支** - 最好不要从你的master分支提交 pull request。
+
+- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。
+
+- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。
+
+## 运行测试
+
+``` bash
+./vendor/bin/phpunit tests/Qiniu/Tests/
+
+```

+ 22 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Qiniu, Ltd.<sdk@qiniu.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 75 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/README.md

@@ -0,0 +1,75 @@
+# Qiniu Cloud SDK for PHP
+[![doxygen.io](http://doxygen.io/github.com/qiniu/php-sdk/?status.svg)](http://doxygen.io/github.com/qiniu/php-sdk/)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
+[![Build Status](https://travis-ci.org/qiniu/php-sdk.svg)](https://travis-ci.org/qiniu/php-sdk)
+[![Latest Stable Version](https://img.shields.io/packagist/v/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk)
+[![Total Downloads](https://img.shields.io/packagist/dt/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[![Code Coverage](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[![Join Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek)
+
+## 安装
+
+* 通过composer,这是推荐的方式,可以使用composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。
+```bash
+$ composer require qiniu/php-sdk
+```
+* 直接下载安装,SDK 没有依赖其他第三方库,但需要参照 composer的autoloader,增加一个自己的autoloader程序。
+
+## 运行环境
+
+| Qiniu SDK版本 | PHP 版本 |
+|:--------------------:|:---------------------------:|
+|          7.x         |  cURL extension,   5.3 - 5.6,7.0 |
+|          6.x         |  cURL extension,   5.2 - 5.6 |
+
+## 使用方法
+
+### 上传
+```php
+use Qiniu\Storage\UploadManager;
+use Qiniu\Auth;
+...
+    $upManager = new UploadManager();
+    $auth = new Auth($accessKey, $secretKey);
+    $token = $auth->uploadToken($bucketName);
+    list($ret, $error) = $upManager->put($token, 'formput', 'hello world');
+...
+```
+
+## 测试
+
+``` bash
+$ ./vendor/bin/phpunit tests/Qiniu/Tests/
+```
+
+## 常见问题
+
+- $error保留了请求响应的信息,失败情况下ret 为none, 将$error可以打印出来,提交给我们。
+- API 的使用 demo 可以参考 [单元测试](https://github.com/qiniu/php-sdk/blob/master/tests)。
+
+## 代码贡献
+
+详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。
+
+## 贡献记录
+
+- [所有贡献者](https://github.com/qiniu/php-sdk/contributors)
+
+## 联系我们
+
+- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件)
+- 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/)
+- 更详细的文档,见[官方文档站](http://developer.qiniu.com/)
+- 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果要提交代码,欢迎提交 pull request
+- 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。
+
+## 代码许可
+
+The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE).
+
+[packagist]: http://packagist.org
+[install-packagist]: https://packagist.org/packages/qiniu/php-sdk

+ 14 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/autoload.php

@@ -0,0 +1,14 @@
+<?php
+
+function classLoader($class)
+{
+    $path = str_replace('\\', DIRECTORY_SEPARATOR, $class);
+    $file = __DIR__ . '/src/' . $path . '.php';
+
+    if (file_exists($file)) {
+        require_once $file;
+    }
+}
+spl_autoload_register('classLoader');
+
+require_once  __DIR__ . '/src/Qiniu/functions.php';

+ 26 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "qiniu/php-sdk",
+    "type": "library",
+    "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+    "keywords": ["qiniu", "storage", "sdk", "cloud"],
+    "homepage": "http://developer.qiniu.com/",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Qiniu",
+            "email": "sdk@qiniu.com",
+            "homepage": "http://www.qiniu.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "~4.0",
+        "squizlabs/php_codesniffer": "~2.3"
+    },
+    "autoload": {
+        "psr-4": {"Qiniu\\": "src/Qiniu"},
+        "files": ["src/Qiniu/functions.php"]
+    }
+}

+ 71 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/docs/rtc/README.md

@@ -0,0 +1,71 @@
+# Rtc Streaming Cloud Server-Side Library For PHP
+
+## Features
+
+- Appclient
+    - [x] 创建房间: client->createApp()
+    - [x] 查看房间: client->getApp()
+    - [x] 删除房间: client->deleteApp()
+    - [x] 生成房间token: client->appToken()
+
+
+
+## Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+    - [Configuration](#configuration)
+    - [App](#app)
+        - [Create a app](#create-a-app)
+        - [Get a app](#get-a-app)
+        - [Delete a app](#delete-a-app)
+        - [Generate a app token](#generate-a-app-token)
+
+
+## Usage
+
+### App
+
+#### Create a app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->createApp("901","testApp");
+print_r($resp);
+```
+
+#### Get an app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->getApp("deq02uhb6");
+print_r($resp);
+```
+
+#### Delete an app
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->deleteApp("deq02uhb6");
+print_r($resp);
+```
+
+#### Generate an app token
+
+```php
+$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5";
+$sk = "xxxx";
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$resp=$client->appToken("deq02uhb6", "lfx", '1111', (time()+3600), 'user');
+print_r($resp);
+```

+ 42 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/docs/rtc/example.php

@@ -0,0 +1,42 @@
+<?php
+require_once("../../autoload.php");
+
+use \Qiniu\Auth;
+
+$ak = 'gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5';
+$sk = 'xxxx';
+
+$auth = new Auth($ak, $sk);
+$client = new Qiniu\Rtc\AppClient($auth);
+$hub = 'lfxlive';
+$title = 'lfxl';
+try {
+    //创建app
+    $resp = $client->createApp($hub, $title, $maxUsers);
+    print_r($resp);
+    // 获取app状态
+    $resp = $client->getApp('dgdl5ge8y');
+    print_r($resp);
+    //修改app状态
+    $mergePublishRtmp = null;
+    $mergePublishRtmp['enable'] = true;
+    $resp = $client->updateApp('dgdl5ge8y', $hub, $title, $maxUsers, $mergePublishRtmp);
+    print_r($resp);
+    //删除app
+    $resp = $client->deleteApp('dgdl5ge8y');
+    print_r($resp);
+    //获取房间连麦的成员
+    $resp=$client->listUser("dgbfvvzid", 'lfxl');
+    print_r($resp);
+    //剔除房间的连麦成员
+    $resp=$client->kickUser("dgbfvvzid", 'lfx', "qiniu-f6e07b78-4dc8-45fb-a701-a9e158abb8e6");
+    print_r($resp);
+    // 列举房间
+    $resp=$client->listActiveRooms("dgbfvvzid", 'lfx', null, null);
+    print_r($resp);
+    //鉴权的有效时间: 1个小时.
+    $resp = $client->appToken("dgd4vecde", "lfxl", '1111', (time()+3600), 'user');
+    print_r($resp);
+} catch (\Exception $e) {
+    echo "Error:", $e, "\n";
+}

+ 10 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/examples/README.md

@@ -0,0 +1,10 @@
+# examples
+
+这些 examples 旨在帮助你快速了解使用七牛的sdk。这些demo都是可以直接运行的, 但是在运行之前需要填上您自己的参数。
+
+比如:
+
+* `$bucket`  需要填上您想操作的 [bucket名字](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket)。
+* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/setting/key)找到。
+* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/mps/pipeline)新建。
+

+ 40 - 0
public/plugins/qiniu/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php

@@ -0,0 +1,40 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+//获取流量和带宽数据
+//参考文档:http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+
+$domains = array(
+    "javasdk.qiniudn.com",
+    "phpsdk.qiniudn.com"
+);
+
+$startDate = "2017-08-20";
+$endDate = "2017-08-21";
+
+//5min or hour or day
+$granularity = "day";
+
+//获取带宽数据
+list($bandwidthData, $getBandwidthErr) = $cdnManager->getBandwidthData(
+    $domains,
+    $startDate,
+    $endDate,
+    $granularity
+);
+
+if ($getBandwidthErr != null) {
+    var_dump($getBandwidthErr);
+} else {
+    echo "get bandwidth data success\n";
+    print_r($bandwidthData);
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff