ソースを参照

项目初始化

linwu 4 ヶ月 前
コミット
88ef5a07a6
100 ファイル変更17049 行追加0 行削除
  1. 2 0
      .dockerignore
  2. 15 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. 76 0
      app/admin/controller/MainController.php
  29. 376 0
      app/admin/controller/UserController.php
  30. 33 0
      app/admin/validate/UserValidate.php
  31. 49 0
      app/common/Constant.php
  32. 92 0
      app/common/Excel.php
  33. 121 0
      app/common/Fun.php
  34. 4977 0
      app/common/Wechat.php
  35. 287 0
      app/love/controller/ActiveController.php
  36. 213 0
      app/love/controller/AdminActiveController.php
  37. 175 0
      app/love/controller/AdminLotteryController.php
  38. 50 0
      app/love/controller/AdminSelectController.php
  39. 123 0
      app/love/controller/LoginController.php
  40. 143 0
      app/love/controller/LotteryController.php
  41. 75 0
      app/love/controller/LoveBaseController.php
  42. 331 0
      app/love/controller/MessageController.php
  43. 857 0
      app/love/controller/MyController.php
  44. 399 0
      app/love/controller/RegisterController.php
  45. 266 0
      app/love/controller/SceneController.php
  46. 484 0
      app/love/controller/UserwallController.php
  47. 28 0
      app/love/model/ActiveApplyModel.php
  48. 20 0
      app/love/model/ActiveModel.php
  49. 19 0
      app/love/model/GiftModel.php
  50. 27 0
      app/love/model/LotteryLogModel.php
  51. 18 0
      app/love/model/LotteryModel.php
  52. 18 0
      app/love/model/LotteryPrizeModel.php
  53. 18 0
      app/love/model/UserAuthModel.php
  54. 23 0
      app/love/model/UserFavoriteModel.php
  55. 23 0
      app/love/model/UserFriendModel.php
  56. 32 0
      app/love/model/UserGiftModel.php
  57. 27 0
      app/love/model/UserInviteModel.php
  58. 27 0
      app/love/model/UserMarryModel.php
  59. 18 0
      app/love/model/UserMatingModel.php
  60. 28 0
      app/love/model/UserMessageModel.php
  61. 28 0
      app/love/model/UserModel.php
  62. 32 0
      app/love/model/UserSelectLogModel.php
  63. 28 0
      app/love/model/UserSelectModel.php
  64. 27 0
      app/love/model/UserVisitModel.php
  65. 67 0
      app/portal/api/CategoryApi.php
  66. 76 0
      app/portal/api/PageApi.php
  67. 460 0
      app/portal/controller/AdminArticleController.php
  68. 370 0
      app/portal/controller/AdminCategoryController.php
  69. 32 0
      app/portal/controller/AdminIndexController.php
  70. 239 0
      app/portal/controller/AdminPageController.php
  71. 153 0
      app/portal/controller/AdminTagController.php
  72. 102 0
      app/portal/controller/ArticleController.php
  73. 104 0
      app/portal/controller/IndexController.php
  74. 39 0
      app/portal/controller/ListController.php
  75. 44 0
      app/portal/controller/PageController.php
  76. 32 0
      app/portal/controller/SearchController.php
  77. 47 0
      app/portal/controller/TagController.php
  78. 113 0
      app/portal/data/portal.sql
  79. 96 0
      app/portal/hooks.php
  80. 13 0
      app/portal/lang/en-us.php
  81. 15 0
      app/portal/lang/en-us/common.php
  82. 21 0
      app/portal/lang/zh-cn.php
  83. 16 0
      app/portal/lang/zh-cn/common.php
  84. 13 0
      app/portal/lang/zh-cn/home.php
  85. 217 0
      app/portal/model/PortalCategoryModel.php
  86. 400 0
      app/portal/model/PortalPostModel.php
  87. 21 0
      app/portal/model/PortalTagModel.php
  88. 23 0
      app/portal/model/UserModel.php
  89. 14 0
      app/portal/nav.php
  90. 459 0
      app/portal/service/ApiService.php
  91. 287 0
      app/portal/service/PostService.php
  92. 372 0
      app/portal/taglib/Portal.php
  93. 53 0
      app/portal/url.php
  94. 15 0
      app/portal/user_action.php
  95. 30 0
      app/portal/validate/AdminArticleValidate.php
  96. 51 0
      app/portal/validate/AdminPageValidate.php
  97. 55 0
      app/portal/validate/PortalCategoryValidate.php
  98. 211 0
      app/test/controller/IndexController.php
  99. 332 0
      app/user/controller/AdminIndexController.php
  100. 213 0
      app/user/controller/AdminUserController.php

+ 2 - 0
.dockerignore

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

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+.buildpath
+.DS_Store
+.project
+.settings
+.idea
+.git
+/.env
+/build
+/public/assets/dist
+/node_modules
+Vagrantfile
+.vagrant
+/data/runtime/temp
+/data/runtime/log
+/data/runtime/cache

+ 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']
+    ];
+}

+ 76 - 0
app/admin/controller/MainController.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: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\admin\controller;
+
+use app\admin\model\UserModel;
+use app\common\Fun;
+use app\love\model\UserAuthModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+class MainController extends AdminBaseController
+{
+
+    /**
+     *  后台欢迎页
+     */
+    public function index()
+    {
+        $auth      = [
+            'total'   => 0,
+            'man'   => 0,
+            'woman' => 0,
+        ];
+        $auth_list = UserAuthModel::column('idcard');
+        foreach ($auth_list as $v) {
+            $auth['total']++;
+            Fun::getSexByIdCard($v) == 1 ? $auth['man']++ : $auth['woman']++;
+        }
+
+        $user = [];
+        $user_where = [
+            ['user_type', '=', 2],
+            ['is_complete', '=', 1],
+        ];
+        $user['total'] = UserModel::where($user_where)->count();
+        $user['man'] = UserModel::where($user_where)->where('sex',1)->count();
+        $user['woman'] = UserModel::where($user_where)->where('sex',2)->count();
+
+        $config = Db::name('config')->find();
+
+        $this->assign('auth',$auth);
+        $this->assign('user',$user);
+        $this->assign('config',$config);
+
+        return $this->fetch();
+    }
+
+    public function dashboardWidget()
+    {
+        $dashboardWidgets = [];
+        $widgets          = $this->request->param('widgets/a');
+        if (!empty($widgets)) {
+            foreach ($widgets as $widget) {
+                if ($widget['is_system']) {
+                    array_push($dashboardWidgets, ['name' => $widget['name'], 'is_system' => 1]);
+                } else {
+                    array_push($dashboardWidgets, ['name' => $widget['name'], 'is_system' => 0]);
+                }
+            }
+        }
+
+        cmf_set_option('admin_dashboard_widgets', $dashboardWidgets, true);
+
+        $this->success('更新成功!');
+
+    }
+
+}

+ 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'],
+    ];
+}

+ 49 - 0
app/common/Constant.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace app\common;
+
+class Constant
+{
+    const SEX          = ['保密', '男', '女'];
+    const MARRY        = ['未婚', '离婚', '丧偶'];
+    const TYPE         = ['交朋友', '找知己', '谈恋爱', '结婚'];
+    const EDUCATION    = ['本科以下', '本科', '硕士', '博士'];
+    const DEPARTMENT   = ['泉州妇联', '集成电路'];
+    const TINYINT      = ['保密', '是', '否'];
+    const COND_TINYINT = ['不限', '是', '否'];
+//    const JOB = ['销售','IT工程师','教授','在校学生','产品经理','总经理','副总/总监','部门经理','中层管理','企业家','个体老板','高级干部','公务员','律师','医生','护士','专家学者','工程师','设计师','艺术家','演员','模特','离/退休','技术员','服务员','普通员工','自由职业','无业'];
+    const INCOME      = ['保密', '5万以下', '5-10万', '10-20万', '20-30万', '30-50万', '50-100万', '100万以上'];
+    const COND_INCOME = ['不限', '5万以下', '5-10万', '10-20万', '20-30万', '30-50万', '50-100万', '100万以上'];
+    const NATION      = ['汉族', '藏族', '朝鲜族', '蒙古族', '回族', '满族', '维吾尔族', '壮族', '彝族', '苗族', '其他民族'];
+    const FAITH       = ['无宗教信仰', '大乘佛教显宗', '大乘佛教密宗', '小乘佛教', '道教', '儒教', '基督教天主教派', '基督教东正教派', '基督教新教派', '犹太教', '伊斯兰教什叶派', '伊斯兰教逊尼派', '印度教', '神道教', '萨满教', '其他宗教信仰'];
+    const SMOKE       = ['不吸,很反感', '不吸烟,但不反感', '社交时偶尔吸烟', '烟不离手'];
+    const DRINK       = ['不喝酒', '社交需要喝', '兴致时小酌', '酒不离口'];
+//    const COMPANY_NATURE = ['政府机关','事业单位','外企企业','世界500强','上市公司','国有企业','私营企业','自有公司'];
+//    const TOPHOME = ['独生子女','老大','老二','老三','老四','老五','其他'];
+//    const WANTCHILD = ['愿意','不愿意','视情况而定'];
+//    const LIVEPARENT = ['愿意','不愿意','视情况而定'];
+    const ID_TYPE = ['公务员', '事业干部', '编外人员', '国企高管', '职工'];
+    const FAMILY  = ['爷爷', '奶奶', '外公', '外婆', '父亲', '母亲', '哥哥', '弟弟', '姐姐', '妹妹'];
+//    const TALENT_LEVEL = ['第一层次', '第二层次', '第三层次', '第四层次', '第五层次', '第六层次', '第七层次', '其他人才', '不是人才'];
+    const NATIVE = ['晋江籍', '非晋江籍', '无要求'];
+    const HIGH   = ['保密', 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200];
+    const WEIGHT = ['保密', 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100];
+
+    /**
+     * 年龄范围
+     */
+    const MIN_AGE = ['不限', 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65];
+    const MAX_AGE = ['不限', 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65];
+
+    /**
+     * 身高范围
+     */
+    const MIN_HIGH = ['不限', 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200];
+    const MAX_HIGH = ['不限', 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200];
+
+    /**
+     * 体重范围
+     */
+    const MIN_WEIGHT = ['不限', 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100];
+    const MAX_WEIGHT = ['不限', 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100];
+}

+ 92 - 0
app/common/Excel.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace app\common;
+
+class Excel
+{
+    public function __construct()
+    {
+        include_once(CMF_ROOT . '/extend/Excel/PHPExcel.php');
+    }
+
+    public function import($file = '', $cell = [], $crop = 0, $sheet = 0)
+    {
+        $file = iconv("utf-8", "gb2312", $file);   //转码
+        if (empty($file) OR !file_exists($file)) {
+            die('file not exists!');
+        }
+
+        $objRead = new \PHPExcel_Reader_Excel2007();   //建立reader对象
+        if (!$objRead->canRead($file)) {
+            $objRead = new \PHPExcel_Reader_Excel5();
+            if (!$objRead->canRead($file)) {
+                die('No Excel!');
+            }
+        }
+
+        $cellName  = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ'];
+        $obj       = $objRead->load($file);  //建立excel对象
+        $currSheet = $obj->getSheet($sheet);   //获取指定的sheet表
+        $columnH   = $currSheet->getHighestColumn();   //取得最大的列号
+        $columnCnt = array_search($columnH, $cellName);
+        $rowCnt    = $currSheet->getHighestRow();   //获取总行数
+
+        $data = [];
+        for ($_row = 1; $_row <= $rowCnt; $_row++) {  //读取内容
+            if ($_row > $crop) {
+                for ($_column = 0; $_column <= $columnCnt; $_column++) {
+                    $cellId    = $cellName[$_column] . $_row;
+                    $cellValue = $currSheet->getCell($cellId)->getValue();
+                    //$cellValue = $currSheet->getCell($cellId)->getCalculatedValue();  #获取公式计算的值
+                    if ($cellValue instanceof \PHPExcel_RichText) {   //富文本转换字符串
+                        $cellValue = $cellValue->__toString();
+                    } else {
+                        $cellValue = (string)$cellValue;
+                    }
+                    if (!empty($cell[$_column])) {
+                        $data[$_row][$cell[$_column]] = $cellValue;
+                    } else {
+                        $data[$_row][] = $cellValue;
+                    }
+                }
+            }
+        }
+
+        return array_values($data);
+    }
+
+    /**
+     * @param $expTitle
+     * @param $expCellName [['a','A']]
+     * @param $expTableData [['a'=>1]]
+     * @param $textValue ['a']
+     */
+    public function export($expTitle, $expCellName, $expTableData, $textValue=[])
+    {
+        $xlsTitle    = iconv('utf-8', 'gb2312', $expTitle); //文件名称
+        $fileName    = $expTitle . date('_YmdHis'); //or $xlsTitle 文件名称可根据自己情况设定
+        $cellNum     = count($expCellName);
+        $dataNum     = count($expTableData);
+        $objPHPExcel = new \PHPExcel();
+        $cellName    = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', 'AO', 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AV', 'AW', 'AX', 'AY', 'AZ'];
+        for ($i = 0; $i < $cellNum; $i++) {
+            $objPHPExcel->setActiveSheetIndex(0)->setCellValue($cellName[$i] . '1', $expCellName[$i][1]);
+        }
+        // Miscellaneous glyphs, UTF-8
+        for ($i = 0; $i < $dataNum; $i++) {
+            for ($j = 0; $j < $cellNum; $j++) {
+                if (in_array($expCellName[$j][0],$textValue)) {
+                    $objPHPExcel->getActiveSheet(0)->setCellValueExplicit($cellName[$j] . ($i + 2), $expTableData[$i][$expCellName[$j][0]],\PHPExcel_Cell_DataType::TYPE_STRING);
+                } else {
+                    $objPHPExcel->getActiveSheet(0)->setCellValue($cellName[$j] . ($i + 2), $expTableData[$i][$expCellName[$j][0]]);
+                }
+            }
+        }
+        header('pragma:public');
+        header('Content-type:application/vnd.ms-excel;charset=utf-8;name="' . $xlsTitle . '.xls"');
+        header("Content-Disposition:attachment;filename=$fileName.xls"); //attachment新窗口打印inline本窗口打印
+        $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
+        $objWriter->save('php://output');
+        exit;
+    }
+}

+ 121 - 0
app/common/Fun.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace app\common;
+
+class Fun
+{
+    /**
+     * 通过身份证号返回男1 女2
+     */
+    public static function getSexByIdCard($idcard)
+    {
+        $sexint = (int)substr($idcard, 16, 1);
+        return $sexint % 2 === 0 ? 2 : 1;
+    }
+
+    /**
+     * 通过身份证号返回生日
+     */
+    public static function getBirthDayByIdCard($idcard)
+    {
+        $birthday = substr($idcard, 6, 4) . '-' . substr($idcard, 10, 2) . '-' . substr($idcard, 12, 2);
+        return $birthday;
+    }
+
+    /**
+     * 通过身份证号返回年龄
+     */
+    public static function getAgeByIdCard($idcard)
+    {
+        $birth = substr($idcard, 6, 4);
+        $year  = date('Y');
+
+        return $year - $birth;
+    }
+
+    /**
+     * 身份证号是否正确
+     */
+    public static function isIdCard($idcard)
+    {
+        $vCity = [
+            '11', '12', '13', '14', '15', '21', '22',
+            '23', '31', '32', '33', '34', '35', '36',
+            '37', '41', '42', '43', '44', '45', '46',
+            '50', '51', '52', '53', '54', '61', '62',
+            '63', '64', '65', '71', '81', '82', '91',
+        ];
+        if (!preg_match('/^([\d]{17}[xX\d]|[\d]{15})$/', $idcard)) return false;
+        if (!in_array(substr($idcard, 0, 2), $vCity)) return false;
+        $vStr    = preg_replace('/[xX]$/i', 'a', $idcard);
+        $vLength = strlen($vStr);
+        if ($vLength == 18) {
+            $vBirthday = substr($vStr, 6, 4) . '-' . substr($vStr, 10, 2) . '-' . substr($vStr, 12, 2);
+        } else {
+            $vBirthday = '19' . substr($vStr, 6, 2) . '-' . substr($vStr, 8, 2) . '-' . substr($vStr, 10, 2);
+        }
+        if (date('Y-m-d', strtotime($vBirthday)) != $vBirthday) return false;
+        if ($vLength == 18) {
+            $vSum = 0;
+            for ($i = 17; $i >= 0; $i--) {
+                $vSubStr = substr($vStr, 17 - $i, 1);
+                $vSum    += (pow(2, $i) % 11) * (($vSubStr == 'a') ? 10 : intval($vSubStr, 11));
+            }
+            if ($vSum % 11 != 1) return false;
+        }
+        return true;
+    }
+
+    /**
+     * 是否手机号
+     */
+    public static function isMobile($user_mobile)
+    {
+        $chars = "/^((\(\d{2,3}\))|(\d{3}\-))?1(3|4|5|6|7|8|9)\d{9}$/";
+        if (preg_match($chars, $user_mobile)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 根据生日获取年龄
+     */
+    public static function getAgeByBirth($birthday)
+    {
+        if (empty($birthday)) {
+            return '未知';
+        }
+        //格式化出生时间年月日
+        $byear  = date('Y', $birthday);
+        $bmonth = date('m', $birthday);
+        $bday   = date('d', $birthday);
+
+        //格式化当前时间年月日
+        $tyear  = date('Y');
+        $tmonth = date('m');
+        $tday   = date('d');
+
+        //开始计算年龄
+        $age = $tyear - $byear;
+        if ($bmonth > $tmonth || $bmonth == $tmonth && $bday > $tday) {
+            $age--;
+        }
+        return $age;
+    }
+
+    /**
+     * 获取加密姓名
+     */
+    public static function getEncodeName($name)
+    {
+        $len = mb_strlen($name,'utf-8');
+        $res = mb_substr($name,0,1);
+        for ($i = 1; $i < $len; $i++) {
+            $res .= '*';
+        }
+
+        return $res;
+    }
+}

+ 4977 - 0
app/common/Wechat.php

@@ -0,0 +1,4977 @@
+<?php
+/**
+ *    微信公众平台PHP-SDK, 官方API部分
+ * @author  dodge <dodgepudding@gmail.com>
+ * @link https://github.com/dodgepudding/wechat-php-sdk
+ * @version 1.2
+ *  usage:
+ *   $options = array(
+ *            'token'=>'tokenaccesskey', //填写你设定的key
+ *            'encodingaeskey'=>'encodingaeskey', //填写加密用的EncodingAESKey
+ *            'appid'=>'wxdk1234567890', //填写高级调用功能的app id
+ *            'appsecret'=>'xxxxxxxxxxxxxxxxxxx' //填写高级调用功能的密钥
+ *        );
+ *     $weObj = new Wechat($options);
+ *   $weObj->valid();
+ *   $type = $weObj->getRev()->getRevType();
+ *   switch($type) {
+ *        case Wechat::MSGTYPE_TEXT:
+ *            $weObj->text("hello, I'm wechat")->reply();
+ *            exit;
+ *            break;
+ *        case Wechat::MSGTYPE_EVENT:
+ *            ....
+ *            break;
+ *        case Wechat::MSGTYPE_IMAGE:
+ *            ...
+ *            break;
+ *        default:
+ *            $weObj->text("help info")->reply();
+ *   }
+ *
+ *   //获取菜单操作:
+ *   $menu = $weObj->getMenu();
+ *   //设置菜单
+ *   $newmenu =  array(
+ *        "button"=>
+ *            array(
+ *                array('type'=>'click','name'=>'最新消息','key'=>'MENU_KEY_NEWS'),
+ *                array('type'=>'view','name'=>'我要搜索','url'=>'http://www.baidu.com'),
+ *                )
+ *        );
+ *   $result = $weObj->createMenu($newmenu);
+ */
+
+namespace app\common;
+
+use think\facade\Cache;
+use think\Db;
+use think\facade\Log;
+
+class Wechat
+{
+    const MSGTYPE_TEXT = 'text';
+    const MSGTYPE_IMAGE = 'image';
+    const MSGTYPE_LOCATION = 'location';
+    const MSGTYPE_LINK = 'link';
+    const MSGTYPE_EVENT = 'event';
+    const MSGTYPE_MUSIC = 'music';
+    const MSGTYPE_NEWS = 'news';
+    const MSGTYPE_VOICE = 'voice';
+    const MSGTYPE_VIDEO = 'video';
+    const MSGTYPE_SHORTVIDEO = 'shortvideo';
+    const EVENT_SUBSCRIBE = 'subscribe';       //订阅
+    const EVENT_UNSUBSCRIBE = 'unsubscribe';   //取消订阅
+    const EVENT_SCAN = 'SCAN';                 //扫描带参数二维码
+    const EVENT_LOCATION = 'LOCATION';         //上报地理位置
+    const EVENT_MENU_VIEW = 'VIEW';                     //菜单 - 点击菜单跳转链接
+    const EVENT_MENU_CLICK = 'CLICK';                   //菜单 - 点击菜单拉取消息
+    const EVENT_MENU_SCAN_PUSH = 'scancode_push';       //菜单 - 扫码推事件(客户端跳URL)
+    const EVENT_MENU_SCAN_WAITMSG = 'scancode_waitmsg'; //菜单 - 扫码推事件(客户端不跳URL)
+    const EVENT_MENU_PIC_SYS = 'pic_sysphoto';          //菜单 - 弹出系统拍照发图
+    const EVENT_MENU_PIC_PHOTO = 'pic_photo_or_album';  //菜单 - 弹出拍照或者相册发图
+    const EVENT_MENU_PIC_WEIXIN = 'pic_weixin';         //菜单 - 弹出微信相册发图器
+    const EVENT_MENU_LOCATION = 'location_select';      //菜单 - 弹出地理位置选择器
+    const EVENT_SEND_MASS = 'MASSSENDJOBFINISH';        //发送结果 - 高级群发完成
+    const EVENT_SEND_TEMPLATE = 'TEMPLATESENDJOBFINISH';//发送结果 - 模板消息发送结果
+    const EVENT_KF_SEESION_CREATE = 'kfcreatesession';  //多客服 - 接入会话
+    const EVENT_KF_SEESION_CLOSE = 'kfclosesession';    //多客服 - 关闭会话
+    const EVENT_KF_SEESION_SWITCH = 'kfswitchsession';  //多客服 - 转接会话
+    const EVENT_CARD_PASS = 'card_pass_check';          //卡券 - 审核通过
+    const EVENT_CARD_NOTPASS = 'card_not_pass_check';   //卡券 - 审核未通过
+    const EVENT_CARD_USER_GET = 'user_get_card';        //卡券 - 用户领取卡券
+    const EVENT_CARD_USER_DEL = 'user_del_card';        //卡券 - 用户删除卡券
+    const EVENT_MERCHANT_ORDER = 'merchant_order';        //微信小店 - 订单付款通知
+    const API_URL_PREFIX = 'https://api.weixin.qq.com/cgi-bin';
+    const AUTH_URL = '/token?grant_type=client_credential&';
+    const MENU_CREATE_URL = '/menu/create?';
+    const MENU_GET_URL = '/menu/get?';
+    const MENU_DELETE_URL = '/menu/delete?';
+    const MENU_ADDCONDITIONAL_URL = '/menu/addconditional?';
+    const MENU_DELCONDITIONAL_URL = '/menu/delconditional?';
+    const MENU_TRYMATCH_URL = '/menu/trymatch?';
+    const GET_TICKET_URL = '/ticket/getticket?';
+    const CALLBACKSERVER_GET_URL = '/getcallbackip?';
+    const QRCODE_CREATE_URL = '/qrcode/create?';
+    const QR_SCENE = 0;
+    const QR_LIMIT_SCENE = 1;
+    const QRCODE_IMG_URL = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=';
+    const SHORT_URL = '/shorturl?';
+    const USER_GET_URL = '/user/get?';
+    const USER_INFO_URL = '/user/info?';
+    const USERS_INFO_URL = '/user/info/batchget?';
+    const USER_UPDATEREMARK_URL = '/user/info/updateremark?';
+    const GROUP_GET_URL = '/groups/get?';
+    const USER_GROUP_URL = '/groups/getid?';
+    const GROUP_CREATE_URL = '/groups/create?';
+    const GROUP_UPDATE_URL = '/groups/update?';
+    const GROUP_MEMBER_UPDATE_URL = '/groups/members/update?';
+    const GROUP_MEMBER_BATCHUPDATE_URL = '/groups/members/batchupdate?';
+    const CUSTOM_SEND_URL = '/message/custom/send?';
+    const MEDIA_UPLOADNEWS_URL = '/media/uploadnews?';
+    const MASS_SEND_URL = '/message/mass/send?';
+    const TEMPLATE_SET_INDUSTRY_URL = '/template/api_set_industry?';
+    const TEMPLATE_ADD_TPL_URL = '/template/api_add_template?';
+    const TEMPLATE_SEND_URL = '/message/template/send?';
+    const MASS_SEND_GROUP_URL = '/message/mass/sendall?';
+    const MASS_DELETE_URL = '/message/mass/delete?';
+    const MASS_PREVIEW_URL = '/message/mass/preview?';
+    const MASS_QUERY_URL = '/message/mass/get?';
+    const UPLOAD_MEDIA_URL = 'http://file.api.weixin.qq.com/cgi-bin';
+    const MEDIA_UPLOAD_URL = '/media/upload?';
+    const MEDIA_UPLOADIMG_URL = '/media/uploadimg?';//图片上传接口
+    const MEDIA_GET_URL = '/media/get?';
+    const MEDIA_VIDEO_UPLOAD = '/media/uploadvideo?';
+    const MEDIA_FOREVER_UPLOAD_URL = '/material/add_material?';
+    const MEDIA_FOREVER_NEWS_UPLOAD_URL = '/material/add_news?';
+    const MEDIA_FOREVER_NEWS_UPDATE_URL = '/material/update_news?';
+    const MEDIA_FOREVER_GET_URL = '/material/get_material?';
+    const MEDIA_FOREVER_DEL_URL = '/material/del_material?';
+    const MEDIA_FOREVER_COUNT_URL = '/material/get_materialcount?';
+    const MEDIA_FOREVER_BATCHGET_URL = '/material/batchget_material?';
+    const OAUTH_PREFIX = 'https://open.weixin.qq.com/connect/oauth2';
+    const OAUTH_AUTHORIZE_URL = '/authorize?';
+    ///多客服相关地址
+    const CUSTOM_SERVICE_GET_RECORD = '/customservice/getrecord?';
+    const CUSTOM_SERVICE_GET_KFLIST = '/customservice/getkflist?';
+    const CUSTOM_SERVICE_GET_ONLINEKFLIST = '/customservice/getonlinekflist?';
+    const API_BASE_URL_PREFIX = 'https://api.weixin.qq.com'; //以下API接口URL需要使用此前缀
+    const OAUTH_TOKEN_URL = '/sns/oauth2/access_token?';
+    const OAUTH_REFRESH_URL = '/sns/oauth2/refresh_token?';
+    const OAUTH_USERINFO_URL = '/sns/userinfo?';
+    const OAUTH_AUTH_URL = '/sns/auth?';
+    ///多客服相关地址
+    const CUSTOM_SESSION_CREATE = '/customservice/kfsession/create?';
+    const CUSTOM_SESSION_CLOSE = '/customservice/kfsession/close?';
+    const CUSTOM_SESSION_SWITCH = '/customservice/kfsession/switch?';
+    const CUSTOM_SESSION_GET = '/customservice/kfsession/getsession?';
+    const CUSTOM_SESSION_GET_LIST = '/customservice/kfsession/getsessionlist?';
+    const CUSTOM_SESSION_GET_WAIT = '/customservice/kfsession/getwaitcase?';
+    const CS_KF_ACCOUNT_ADD_URL = '/customservice/kfaccount/add?';
+    const CS_KF_ACCOUNT_UPDATE_URL = '/customservice/kfaccount/update?';
+    const CS_KF_ACCOUNT_DEL_URL = '/customservice/kfaccount/del?';
+    const CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL = '/customservice/kfaccount/uploadheadimg?';
+    ///卡券相关地址
+    const CARD_CREATE = '/card/create?';
+    const CARD_DELETE = '/card/delete?';
+    const CARD_UPDATE = '/card/update?';
+    const CARD_GET = '/card/get?';
+    const CARD_USER_GETCARDLIST = '/card/user/getcardlist?';
+    const CARD_BATCHGET = '/card/batchget?';
+    const CARD_MODIFY_STOCK = '/card/modifystock?';
+    const CARD_LOCATION_BATCHADD = '/card/location/batchadd?';
+    const CARD_LOCATION_BATCHGET = '/card/location/batchget?';
+    const CARD_GETCOLORS = '/card/getcolors?';
+    const CARD_QRCODE_CREATE = '/card/qrcode/create?';
+    const CARD_CODE_CONSUME = '/card/code/consume?';
+    const CARD_CODE_DECRYPT = '/card/code/decrypt?';
+    const CARD_CODE_GET = '/card/code/get?';
+    const CARD_CODE_UPDATE = '/card/code/update?';
+    const CARD_CODE_UNAVAILABLE = '/card/code/unavailable?';
+    const CARD_TESTWHILELIST_SET = '/card/testwhitelist/set?';
+    const CARD_MEETINGCARD_UPDATEUSER = '/card/meetingticket/updateuser?';    //更新会议门票
+    const CARD_MEMBERCARD_ACTIVATE = '/card/membercard/activate?';      //激活会员卡
+    const CARD_MEMBERCARD_UPDATEUSER = '/card/membercard/updateuser?';    //更新会员卡
+    const CARD_MOVIETICKET_UPDATEUSER = '/card/movieticket/updateuser?';   //更新电影票(未加方法)
+    const CARD_BOARDINGPASS_CHECKIN = '/card/boardingpass/checkin?';     //飞机票-在线选座(未加方法)
+    const CARD_LUCKYMONEY_UPDATE = '/card/luckymoney/updateuserbalance?';     //更新红包金额
+    const SEMANTIC_API_URL = '/semantic/semproxy/search?'; //语义理解
+    ///数据分析接口
+    static $DATACUBE_URL_ARR
+        = [        //用户分析
+            'user'        => [
+                'summary'  => '/datacube/getusersummary?',        //获取用户增减数据(getusersummary)
+                'cumulate' => '/datacube/getusercumulate?',        //获取累计用户数据(getusercumulate)
+            ],
+            'article'     => [            //图文分析
+                'summary'   => '/datacube/getarticlesummary?',        //获取图文群发每日数据(getarticlesummary)
+                'total'     => '/datacube/getarticletotal?',        //获取图文群发总数据(getarticletotal)
+                'read'      => '/datacube/getuserread?',            //获取图文统计数据(getuserread)
+                'readhour'  => '/datacube/getuserreadhour?',        //获取图文统计分时数据(getuserreadhour)
+                'share'     => '/datacube/getusershare?',            //获取图文分享转发数据(getusershare)
+                'sharehour' => '/datacube/getusersharehour?',        //获取图文分享转发分时数据(getusersharehour)
+            ],
+            'upstreammsg' => [        //消息分析
+                'summary'   => '/datacube/getupstreammsg?',        //获取消息发送概况数据(getupstreammsg)
+                'hour'      => '/datacube/getupstreammsghour?',    //获取消息分送分时数据(getupstreammsghour)
+                'week'      => '/datacube/getupstreammsgweek?',    //获取消息发送周数据(getupstreammsgweek)
+                'month'     => '/datacube/getupstreammsgmonth?',    //获取消息发送月数据(getupstreammsgmonth)
+                'dist'      => '/datacube/getupstreammsgdist?',    //获取消息发送分布数据(getupstreammsgdist)
+                'distweek'  => '/datacube/getupstreammsgdistweek?',    //获取消息发送分布周数据(getupstreammsgdistweek)
+                'distmonth' => '/datacube/getupstreammsgdistmonth?',    //获取消息发送分布月数据(getupstreammsgdistmonth)
+            ],
+            'interface'   => [        //接口分析
+                'summary'     => '/datacube/getinterfacesummary?',    //获取接口分析数据(getinterfacesummary)
+                'summaryhour' => '/datacube/getinterfacesummaryhour?',    //获取接口分析分时数据(getinterfacesummaryhour)
+            ],
+        ];
+    ///微信摇一摇周边
+    const SHAKEAROUND_DEVICE_APPLYID = '/shakearound/device/applyid?';//申请设备ID
+    const SHAKEAROUND_DEVICE_UPDATE = '/shakearound/device/update?';//编辑设备信息
+    const SHAKEAROUND_DEVICE_SEARCH = '/shakearound/device/search?';//查询设备列表
+    const SHAKEAROUND_DEVICE_BINDLOCATION = '/shakearound/device/bindlocation?';//配置设备与门店ID的关系
+    const SHAKEAROUND_DEVICE_BINDPAGE = '/shakearound/device/bindpage?';//配置设备与页面的绑定关系
+    const SHAKEAROUND_MATERIAL_ADD = '/shakearound/material/add?';//上传摇一摇图片素材
+    const SHAKEAROUND_PAGE_ADD = '/shakearound/page/add?';//增加页面
+    const SHAKEAROUND_PAGE_UPDATE = '/shakearound/page/update?';//编辑页面
+    const SHAKEAROUND_PAGE_SEARCH = '/shakearound/page/search?';//查询页面列表
+    const SHAKEAROUND_PAGE_DELETE = '/shakearound/page/delete?';//删除页面
+    const SHAKEAROUND_USER_GETSHAKEINFO = '/shakearound/user/getshakeinfo?';//获取摇周边的设备及用户信息
+    const SHAKEAROUND_STATISTICS_DEVICE = '/shakearound/statistics/device?';//以设备为维度的数据统计接口
+    const SHAKEAROUND_STATISTICS_PAGE = '/shakearound/statistics/page?';//以页面为维度的数据统计接口
+    ///微信小店相关接口
+    const MERCHANT_ORDER_GETBYID = '/merchant/order/getbyid?';//根据订单ID获取订单详情
+    const MERCHANT_ORDER_GETBYFILTER = '/merchant/order/getbyfilter?';//根据订单状态/创建时间获取订单详情
+    const MERCHANT_ORDER_SETDELIVERY = '/merchant/order/setdelivery?';//设置订单发货信息
+    const MERCHANT_ORDER_CLOSE = '/merchant/order/close?';//关闭订单
+
+    private $token;
+    private $encodingAesKey;
+    private $encrypt_type;
+    private $appid;
+    private $appsecret;
+    private $access_token;
+    private $jsapi_ticket;
+    private $api_ticket;
+    private $user_token;
+    private $partnerid;
+    private $partnerkey;
+    private $paysignkey;
+    private $postxml;
+    private $_msg;
+    private $_funcflag = false;
+    private $_receive;
+    private $_text_filter = true;
+    public $debug = false;
+    public $errCode = 40001;
+    public $errMsg = "no access";
+    public $logcallback;
+    public $is_dingyue = false; //是否订阅号
+
+    public function __construct($options)
+    {
+        $this->token          = isset($options['token']) ? $options['token'] : '';
+        $this->encodingAesKey = isset($options['encodingaeskey']) ? $options['encodingaeskey'] : '';
+        $this->appid          = isset($options['appid']) ? $options['appid'] : '';
+        $this->appsecret      = isset($options['appsecret']) ? $options['appsecret'] : '';
+        $this->debug          = isset($options['debug']) ? $options['debug'] : false;
+        $this->logcallback    = isset($options['logcallback']) ? $options['logcallback'] : false;
+        $this->is_dingyue     = isset($options['is_dingyue']) ? (intval($options['is_dingyue']) == 1) : false;
+    }
+
+    private static $_instance = null;
+
+    /**
+     * 单例模式的实例
+     * @return bool|Wechat|null
+     */
+    public static function instance()
+    {
+        if (self::$_instance == null) {
+            $setting = [
+                'token' => 'QrxDIl1Iq2JOjZh2JtxTOcjHq2c3rgLB',
+                'app_id' => 'wxa3dec10df68cd87d',
+                'app_secret' => '17bff4aa5bf1ffa2c15aeb5eea0e2b32',
+                'aec_key' => 'Ja4CqKlYMS92vc99S5eSyC99C2235s93S94939oAsAA',
+                'is_dingyue' => 0,
+                'is_bind' => 0,
+            ];
+            if (empty($setting)) {
+                return false;
+            }
+            self::$_instance = new self(
+                [
+                    'token'          => $setting['token'],
+                    'appid'          => $setting['app_id'],
+                    'appsecret'      => $setting['app_secret'],
+                    'encodingaeskey' => $setting['aec_key'],
+                    'is_dingyue'     => $setting['is_dingyue'],
+                ]);
+        }
+        return self::$_instance;
+    }
+
+    /**
+     * For weixin server validation
+     */
+    private function checkSignature($str = '')
+    {
+        $signature = isset($_GET["signature"]) ? $_GET["signature"] : '';
+        $signature = isset($_GET["msg_signature"]) ? $_GET["msg_signature"] : $signature; //如果存在加密验证则用加密验证段
+        $timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : '';
+        $nonce     = isset($_GET["nonce"]) ? $_GET["nonce"] : '';
+
+        $token  = $this->token;
+        $tmpArr = [$token, $timestamp, $nonce, $str];
+        sort($tmpArr, SORT_STRING);
+        $tmpStr = implode($tmpArr);
+        $tmpStr = sha1($tmpStr);
+
+        if ($tmpStr == $signature) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * For weixin server validation
+     * @param bool $return 是否返回
+     */
+    public function valid($return = false)
+    {
+        $encryptStr = "";
+        if ($_SERVER['REQUEST_METHOD'] == "POST") {
+            $postStr            = file_get_contents("php://input");
+            $array              = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+            $this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"] : '';
+            if ($this->encrypt_type == 'aes') { //aes加密
+                $this->log($postStr);
+                $encryptStr = $array['Encrypt'];
+                $pc         = new Prpcrypt($this->encodingAesKey);
+                $array      = $pc->decrypt($encryptStr, $this->appid);
+                if (!isset($array[0]) || ($array[0] != 0)) {
+                    if (!$return) {
+                        die('decrypt error!');
+                    } else {
+                        return false;
+                    }
+                }
+                $this->postxml = $array[1];
+                if (!$this->appid)
+                    $this->appid = $array[2];//为了没有appid的订阅号。
+            } else {
+                $this->postxml = $postStr;
+            }
+        } elseif (isset($_GET["echostr"])) {
+            $echoStr = $_GET["echostr"];
+            if ($return) {
+                if ($this->checkSignature())
+                    return $echoStr;
+                else
+                    return false;
+            } else {
+                if ($this->checkSignature()) {
+                    die($echoStr);
+                } else {
+                    die('no access');
+                }
+            }
+        }
+
+        if (!$this->checkSignature($encryptStr)) {
+            if ($return)
+                return false;
+            else
+                die('no access');
+        }
+        return true;
+    }
+
+    /**
+     * 设置发送消息
+     * @param array $msg 消息数组
+     * @param bool $append 是否在原消息数组追加
+     */
+    public function Message($msg = '', $append = false)
+    {
+        if (is_null($msg)) {
+            $this->_msg = [];
+        } elseif (is_array($msg)) {
+            if ($append)
+                $this->_msg = array_merge($this->_msg, $msg);
+            else
+                $this->_msg = $msg;
+            return $this->_msg;
+        } else {
+            return $this->_msg;
+        }
+    }
+
+    /**
+     * 设置消息的星标标志,官方已取消对此功能的支持
+     */
+    public function setFuncFlag($flag)
+    {
+        $this->_funcflag = $flag;
+        return $this;
+    }
+
+    /**
+     * 日志记录,可被重载。
+     * @param mixed $log 输入日志
+     * @return mixed
+     */
+    protected function log($log)
+    {
+        if ($this->debug && function_exists($this->logcallback)) {
+            if (is_array($log)) $log = print_r($log, true);
+            return call_user_func($this->logcallback, $log);
+        }
+    }
+
+    /**
+     * 获取微信服务器发来的信息
+     */
+    public function getRev()
+    {
+        if ($this->_receive) return $this;
+        $postStr = !empty($this->postxml) ? $this->postxml : file_get_contents("php://input");
+        //兼顾使用明文又不想调用valid()方法的情况
+        $this->log($postStr);
+        if (!empty($postStr)) {
+            $this->_receive = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
+        }
+        return $this;
+    }
+
+    /**
+     * 获取微信服务器发来的信息
+     */
+    public function getRevData()
+    {
+        return $this->_receive;
+    }
+
+    /**
+     * 获取消息发送者
+     */
+    public function getRevFrom()
+    {
+        if (isset($this->_receive['FromUserName']))
+            return $this->_receive['FromUserName'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取消息接受者
+     */
+    public function getRevTo()
+    {
+        if (isset($this->_receive['ToUserName']))
+            return $this->_receive['ToUserName'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取接收消息的类型
+     */
+    public function getRevType()
+    {
+        if (isset($this->_receive['MsgType']))
+            return $this->_receive['MsgType'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取消息ID
+     */
+    public function getRevID()
+    {
+        if (isset($this->_receive['MsgId']))
+            return $this->_receive['MsgId'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取消息发送时间
+     */
+    public function getRevCtime()
+    {
+        if (isset($this->_receive['CreateTime']))
+            return $this->_receive['CreateTime'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取接收消息内容正文
+     */
+    public function getRevContent()
+    {
+        if (isset($this->_receive['Content']))
+            return $this->_receive['Content'];
+        else if (isset($this->_receive['Recognition'])) //获取语音识别文字内容,需申请开通
+            return $this->_receive['Recognition'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取接收消息图片
+     */
+    public function getRevPic()
+    {
+        if (isset($this->_receive['PicUrl']))
+            return [
+                'mediaid' => $this->_receive['MediaId'],
+                'picurl'  => (string)$this->_receive['PicUrl'],    //防止picurl为空导致解析出错
+            ];
+        else
+            return false;
+    }
+
+    /**
+     * 获取接收消息链接
+     */
+    public function getRevLink()
+    {
+        if (isset($this->_receive['Url'])) {
+            return [
+                'url'         => $this->_receive['Url'],
+                'title'       => $this->_receive['Title'],
+                'description' => $this->_receive['Description'],
+            ];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取接收地理位置
+     */
+    public function getRevGeo()
+    {
+        if (isset($this->_receive['Location_X'])) {
+            return [
+                'x'     => $this->_receive['Location_X'],
+                'y'     => $this->_receive['Location_Y'],
+                'scale' => $this->_receive['Scale'],
+                'label' => $this->_receive['Label'],
+            ];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取上报地理位置事件
+     */
+    public function getRevEventGeo()
+    {
+        if (isset($this->_receive['Latitude'])) {
+            return [
+                'x'         => $this->_receive['Latitude'],
+                'y'         => $this->_receive['Longitude'],
+                'precision' => $this->_receive['Precision'],
+            ];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取接收事件推送
+     */
+    public function getRevEvent()
+    {
+        if (isset($this->_receive['Event'])) {
+            $array['event'] = $this->_receive['Event'];
+        }
+        if (isset($this->_receive['EventKey'])) {
+            $array['key'] = $this->_receive['EventKey'];
+        }
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取自定义菜单的扫码推事件信息
+     *
+     * 事件类型为以下两种时则调用此方法有效
+     * Event     事件类型,scancode_push
+     * Event     事件类型,scancode_waitmsg
+     *
+     * @return: array | false
+     * array (
+     *     'ScanType'=>'qrcode',
+     *     'ScanResult'=>'123123'
+     * )
+     */
+    public function getRevScanInfo()
+    {
+        if (isset($this->_receive['ScanCodeInfo'])) {
+            if (!is_array($this->_receive['ScanCodeInfo'])) {
+                $array                          = (array)$this->_receive['ScanCodeInfo'];
+                $this->_receive['ScanCodeInfo'] = $array;
+            } else {
+                $array = $this->_receive['ScanCodeInfo'];
+            }
+        }
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取自定义菜单的图片发送事件信息
+     *
+     * 事件类型为以下三种时则调用此方法有效
+     * Event     事件类型,pic_sysphoto        弹出系统拍照发图的事件推送
+     * Event     事件类型,pic_photo_or_album  弹出拍照或者相册发图的事件推送
+     * Event     事件类型,pic_weixin          弹出微信相册发图器的事件推送
+     *
+     * @return: array | false
+     * array (
+     *   'Count' => '2',
+     *   'PicList' =>array (
+     *         'item' =>array (
+     *             0 =>array ('PicMd5Sum' => 'aaae42617cf2a14342d96005af53624c'),
+     *             1 =>array ('PicMd5Sum' => '149bd39e296860a2adc2f1bb81616ff8'),
+     *         ),
+     *   ),
+     * )
+     *
+     */
+    public function getRevSendPicsInfo()
+    {
+        if (isset($this->_receive['SendPicsInfo'])) {
+            if (!is_array($this->_receive['SendPicsInfo'])) {
+                $array = (array)$this->_receive['SendPicsInfo'];
+                if (isset($array['PicList'])) {
+                    $array['PicList']         = (array)$array['PicList'];
+                    $item                     = $array['PicList']['item'];
+                    $array['PicList']['item'] = [];
+                    foreach ($item as $key => $value) {
+                        $array['PicList']['item'][$key] = (array)$value;
+                    }
+                }
+                $this->_receive['SendPicsInfo'] = $array;
+            } else {
+                $array = $this->_receive['SendPicsInfo'];
+            }
+        }
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取自定义菜单的地理位置选择器事件推送
+     *
+     * 事件类型为以下时则可以调用此方法有效
+     * Event     事件类型,location_select        弹出地理位置选择器的事件推送
+     *
+     * @return: array | false
+     * array (
+     *   'Location_X' => '33.731655000061',
+     *   'Location_Y' => '113.29955200008047',
+     *   'Scale' => '16',
+     *   'Label' => '某某市某某区某某路',
+     *   'Poiname' => '',
+     * )
+     *
+     */
+    public function getRevSendGeoInfo()
+    {
+        if (isset($this->_receive['SendLocationInfo'])) {
+            if (!is_array($this->_receive['SendLocationInfo'])) {
+                $array = (array)$this->_receive['SendLocationInfo'];
+                if (empty($array['Poiname'])) {
+                    $array['Poiname'] = "";
+                }
+                if (empty($array['Label'])) {
+                    $array['Label'] = "";
+                }
+                $this->_receive['SendLocationInfo'] = $array;
+            } else {
+                $array = $this->_receive['SendLocationInfo'];
+            }
+        }
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取接收语音推送
+     */
+    public function getRevVoice()
+    {
+        if (isset($this->_receive['MediaId'])) {
+            return [
+                'mediaid' => $this->_receive['MediaId'],
+                'format'  => $this->_receive['Format'],
+            ];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取接收视频推送
+     */
+    public function getRevVideo()
+    {
+        if (isset($this->_receive['MediaId'])) {
+            return [
+                'mediaid'      => $this->_receive['MediaId'],
+                'thumbmediaid' => $this->_receive['ThumbMediaId'],
+            ];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取接收TICKET
+     */
+    public function getRevTicket()
+    {
+        if (isset($this->_receive['Ticket'])) {
+            return $this->_receive['Ticket'];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取二维码的场景值
+     */
+    public function getRevSceneId()
+    {
+        if (isset($this->_receive['EventKey'])) {
+            return str_replace('qrscene_', '', $this->_receive['EventKey']);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取主动推送的消息ID
+     * 经过验证,这个和普通的消息MsgId不一样
+     * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH
+     */
+    public function getRevTplMsgID()
+    {
+        if (isset($this->_receive['MsgID'])) {
+            return $this->_receive['MsgID'];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取模板消息发送状态
+     */
+    public function getRevStatus()
+    {
+        if (isset($this->_receive['Status'])) {
+            return $this->_receive['Status'];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取群发或模板消息发送结果
+     * 当Event为 MASSSENDJOBFINISH 或 TEMPLATESENDJOBFINISH,即高级群发/模板消息
+     */
+    public function getRevResult()
+    {
+        if (isset($this->_receive['Status'])) //发送是否成功,具体的返回值请参考 高级群发/模板消息 的事件推送说明
+            $array['Status'] = $this->_receive['Status'];
+        if (isset($this->_receive['MsgID'])) //发送的消息id
+            $array['MsgID'] = $this->_receive['MsgID'];
+
+        //以下仅当群发消息时才会有的事件内容
+        if (isset($this->_receive['TotalCount']))     //分组或openid列表内粉丝数量
+            $array['TotalCount'] = $this->_receive['TotalCount'];
+        if (isset($this->_receive['FilterCount']))    //过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数
+            $array['FilterCount'] = $this->_receive['FilterCount'];
+        if (isset($this->_receive['SentCount']))     //发送成功的粉丝数
+            $array['SentCount'] = $this->_receive['SentCount'];
+        if (isset($this->_receive['ErrorCount']))    //发送失败的粉丝数
+            $array['ErrorCount'] = $this->_receive['ErrorCount'];
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取多客服会话状态推送事件 - 接入会话
+     * 当Event为 kfcreatesession 即接入会话
+     * @return string | boolean  返回分配到的客服
+     */
+    public function getRevKFCreate()
+    {
+        if (isset($this->_receive['KfAccount'])) {
+            return $this->_receive['KfAccount'];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取多客服会话状态推送事件 - 关闭会话
+     * 当Event为 kfclosesession 即关闭会话
+     * @return string | boolean  返回分配到的客服
+     */
+    public function getRevKFClose()
+    {
+        if (isset($this->_receive['KfAccount'])) {
+            return $this->_receive['KfAccount'];
+        } else
+            return false;
+    }
+
+    /**
+     * 获取多客服会话状态推送事件 - 转接会话
+     * 当Event为 kfswitchsession 即转接会话
+     * @return array | boolean  返回分配到的客服
+     * {
+     *     'FromKfAccount' => '',      //原接入客服
+     *     'ToKfAccount' => ''            //转接到客服
+     * }
+     */
+    public function getRevKFSwitch()
+    {
+        if (isset($this->_receive['FromKfAccount']))     //原接入客服
+            $array['FromKfAccount'] = $this->_receive['FromKfAccount'];
+        if (isset($this->_receive['ToKfAccount']))    //转接到客服
+            $array['ToKfAccount'] = $this->_receive['ToKfAccount'];
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取卡券事件推送 - 卡卷审核是否通过
+     * 当Event为 card_pass_check(审核通过) 或 card_not_pass_check(未通过)
+     * @return string|boolean  返回卡券ID
+     */
+    public function getRevCardPass()
+    {
+        if (isset($this->_receive['CardId']))
+            return $this->_receive['CardId'];
+        else
+            return false;
+    }
+
+    /**
+     * 获取卡券事件推送 - 领取卡券
+     * 当Event为 user_get_card(用户领取卡券)
+     * @return array|boolean
+     */
+    public function getRevCardGet()
+    {
+        if (isset($this->_receive['CardId']))     //卡券 ID
+            $array['CardId'] = $this->_receive['CardId'];
+        if (isset($this->_receive['IsGiveByFriend']))    //是否为转赠,1 代表是,0 代表否。
+            $array['IsGiveByFriend'] = $this->_receive['IsGiveByFriend'];
+        $array['OldUserCardCode'] = $this->_receive['OldUserCardCode'];
+        if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。
+            $array['UserCardCode'] = $this->_receive['UserCardCode'];
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取卡券事件推送 - 删除卡券
+     * 当Event为 user_del_card(用户删除卡券)
+     * @return array|boolean
+     */
+    public function getRevCardDel()
+    {
+        if (isset($this->_receive['CardId']))     //卡券 ID
+            $array['CardId'] = $this->_receive['CardId'];
+        if (isset($this->_receive['UserCardCode']) && !empty($this->_receive['UserCardCode'])) //code 序列号。自定义 code 及非自定义 code的卡券被领取后都支持事件推送。
+            $array['UserCardCode'] = $this->_receive['UserCardCode'];
+        if (isset($array) && count($array) > 0) {
+            return $array;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 获取订单ID - 订单付款通知
+     * 当Event为 merchant_order(订单付款通知)
+     * @return orderId|boolean
+     */
+    public function getRevOrderId()
+    {
+        if (isset($this->_receive['OrderId']))     //订单 ID
+            return $this->_receive['OrderId'];
+        else
+            return false;
+    }
+
+    public static function xmlSafeStr($str)
+    {
+        return '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str) . ']]>';
+    }
+
+    /**
+     * 数据XML编码
+     * @param mixed $data 数据
+     * @return string
+     */
+    public static function data_to_xml($data)
+    {
+        $xml = '';
+        foreach ($data as $key => $val) {
+            is_numeric($key) && $key = "item id=\"$key\"";
+            $xml .= "<$key>";
+            $xml .= (is_array($val) || is_object($val)) ? self::data_to_xml($val) : self::xmlSafeStr($val);
+            list($key,) = explode(' ', $key);
+            $xml .= "</$key>";
+        }
+        return $xml;
+    }
+
+    /**
+     * XML编码
+     * @param mixed $data 数据
+     * @param string $root 根节点名
+     * @param string $item 数字索引的子节点名
+     * @param string $attr 根节点属性
+     * @param string $id 数字索引子节点key转换的属性名
+     * @param string $encoding 数据编码
+     * @return string
+     */
+    public function xml_encode($data, $root = 'xml', $item = 'item', $attr = '', $id = 'id', $encoding = 'utf-8')
+    {
+        if (is_array($attr)) {
+            $_attr = [];
+            foreach ($attr as $key => $value) {
+                $_attr[] = "{$key}=\"{$value}\"";
+            }
+            $attr = implode(' ', $_attr);
+        }
+        $attr = trim($attr);
+        $attr = empty($attr) ? '' : " {$attr}";
+        $xml  = "<{$root}{$attr}>";
+        $xml  .= self::data_to_xml($data, $item, $id);
+        $xml  .= "</{$root}>";
+        return $xml;
+    }
+
+    /**
+     * 过滤文字回复\r\n换行符
+     * @param string $text
+     * @return string|mixed
+     */
+    private function _auto_text_filter($text)
+    {
+        if (!$this->_text_filter) return $text;
+        return str_replace("\r\n", "\n", $text);
+    }
+
+    /**
+     * 设置回复消息
+     * Example: $obj->text('hello')->reply();
+     * @param string $text
+     */
+    public function text($text = '')
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $msg      = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'MsgType'      => self::MSGTYPE_TEXT,
+            'Content'      => $this->_auto_text_filter($text),
+            'CreateTime'   => time(),
+            'FuncFlag'     => $FuncFlag,
+        ];
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 设置回复消息
+     * Example: $obj->image('media_id')->reply();
+     * @param string $mediaid
+     */
+    public function image($mediaid = '')
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $msg      = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'MsgType'      => self::MSGTYPE_IMAGE,
+            'Image'        => ['MediaId' => $mediaid],
+            'CreateTime'   => time(),
+            'FuncFlag'     => $FuncFlag,
+        ];
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 设置回复消息
+     * Example: $obj->voice('media_id')->reply();
+     * @param string $mediaid
+     */
+    public function voice($mediaid = '')
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $msg      = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'MsgType'      => self::MSGTYPE_VOICE,
+            'Voice'        => ['MediaId' => $mediaid],
+            'CreateTime'   => time(),
+            'FuncFlag'     => $FuncFlag,
+        ];
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 设置回复消息
+     * Example: $obj->video('media_id','title','description')->reply();
+     * @param string $mediaid
+     */
+    public function video($mediaid = '', $title = '', $description = '')
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $msg      = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'MsgType'      => self::MSGTYPE_VIDEO,
+            'Video'        => [
+                'MediaId'     => $mediaid,
+                'Title'       => $title,
+                'Description' => $description,
+            ],
+            'CreateTime'   => time(),
+            'FuncFlag'     => $FuncFlag,
+        ];
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 设置回复音乐
+     * @param string $title
+     * @param string $desc
+     * @param string $musicurl
+     * @param string $hgmusicurl
+     * @param string $thumbmediaid 音乐图片缩略图的媒体id,非必须
+     */
+    public function music($title, $desc, $musicurl, $hgmusicurl = '', $thumbmediaid = '')
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $msg      = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'CreateTime'   => time(),
+            'MsgType'      => self::MSGTYPE_MUSIC,
+            'Music'        => [
+                'Title'       => $title,
+                'Description' => $desc,
+                'MusicUrl'    => $musicurl,
+                'HQMusicUrl'  => $hgmusicurl,
+            ],
+            'FuncFlag'     => $FuncFlag,
+        ];
+        if ($thumbmediaid) {
+            $msg['Music']['ThumbMediaId'] = $thumbmediaid;
+        }
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 设置回复图文
+     * @param array $newsData
+     * 数组结构:
+     *  array(
+     *    "0"=>array(
+     *        'Title'=>'msg title',
+     *        'Description'=>'summary text',
+     *        'PicUrl'=>'http://www.domain.com/1.jpg',
+     *        'Url'=>'http://www.domain.com/1.html'
+     *    ),
+     *    "1"=>....
+     *  )
+     */
+    public function news($newsData = [])
+    {
+        $FuncFlag = $this->_funcflag ? 1 : 0;
+        $count    = count($newsData);
+
+        $msg = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'MsgType'      => self::MSGTYPE_NEWS,
+            'CreateTime'   => time(),
+            'ArticleCount' => $count,
+            'Articles'     => $newsData,
+            'FuncFlag'     => $FuncFlag,
+        ];
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     *
+     * 回复微信服务器, 此函数支持链式操作
+     * Example: $this->text('msg tips')->reply();
+     * @param string $msg 要发送的信息, 默认取$this->_msg
+     * @param bool $return 是否返回信息而不抛出到浏览器 默认:否
+     */
+    public function reply($msg = [], $return = false)
+    {
+        if (empty($msg)) {
+            if (empty($this->_msg))   //防止不先设置回复内容,直接调用reply方法导致异常
+                return false;
+            $msg = $this->_msg;
+        }
+        $xmldata = $this->xml_encode($msg);
+        $this->log($xmldata);
+        if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
+            $pc    = new Prpcrypt($this->encodingAesKey);
+            $array = $pc->encrypt($xmldata, $this->appid);
+            $ret   = $array[0];
+            if ($ret != 0) {
+                $this->log('encrypt err!');
+                return false;
+            }
+            $timestamp = time();
+            $nonce     = rand(77, 999) * rand(605, 888) * rand(11, 99);
+            $encrypt   = $array[1];
+            $tmpArr    = [$this->token, $timestamp, $nonce, $encrypt];//比普通公众平台多了一个加密的密文
+            sort($tmpArr, SORT_STRING);
+            $signature = implode($tmpArr);
+            $signature = sha1($signature);
+            $xmldata   = $this->generate($encrypt, $signature, $timestamp, $nonce);
+            $this->log($xmldata);
+        }
+        if ($return)
+            return $xmldata;
+        else
+            echo $xmldata;
+    }
+
+    /**
+     * xml格式加密,仅请求为加密方式时再用
+     */
+    private function generate($encrypt, $signature, $timestamp, $nonce)
+    {
+        //格式化加密信息
+        $format
+            = "<xml>
+<Encrypt><![CDATA[%s]]></Encrypt>
+<MsgSignature><![CDATA[%s]]></MsgSignature>
+<TimeStamp>%s</TimeStamp>
+<Nonce><![CDATA[%s]]></Nonce>
+</xml>";
+        return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
+    }
+
+    /**
+     * GET 请求
+     * @param string $url
+     */
+    private function http_get($url)
+    {
+        $oCurl = curl_init();
+        if (stripos($url, "https://") !== FALSE) {
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+        }
+        curl_setopt($oCurl, CURLOPT_URL, $url);
+        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
+        $sContent = curl_exec($oCurl);
+        $aStatus  = curl_getinfo($oCurl);
+        curl_close($oCurl);
+        if (intval($aStatus["http_code"]) == 200) {
+            return $sContent;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * POST 请求
+     * @param string $url
+     * @param array $param
+     * @param boolean $post_file 是否文件上传
+     * @return string content
+     */
+    private function http_post($url, $param, $post_file = false)
+    {
+        $oCurl = curl_init();
+        if (stripos($url, "https://") !== FALSE) {
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
+            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+        }
+        if (PHP_VERSION_ID >= 50500 && class_exists('\CURLFile')) {
+            $is_curlFile = true;
+        } else {
+            $is_curlFile = false;
+            if (defined('CURLOPT_SAFE_UPLOAD')) {
+                curl_setopt($oCurl, CURLOPT_SAFE_UPLOAD, false);
+            }
+        }
+        if (is_string($param)) {
+            $strPOST = $param;
+        } elseif ($post_file) {
+            if ($is_curlFile) {
+                foreach ($param as $key => $val) {
+                    if (substr($val, 0, 1) == '@') {
+                        $param[$key] = new \CURLFile(realpath(substr($val, 1)));
+                    }
+                }
+            }
+            $strPOST = $param;
+        } else {
+            $aPOST = [];
+            foreach ($param as $key => $val) {
+                $aPOST[] = $key . "=" . urlencode($val);
+            }
+            $strPOST = join("&", $aPOST);
+        }
+        curl_setopt($oCurl, CURLOPT_URL, $url);
+        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($oCurl, CURLOPT_POST, true);
+        curl_setopt($oCurl, CURLOPT_POSTFIELDS, $strPOST);
+        $sContent = curl_exec($oCurl);
+        $aStatus  = curl_getinfo($oCurl);
+        curl_close($oCurl);
+        if (intval($aStatus["http_code"]) == 200) {
+            return $sContent;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 设置缓存,按需重载
+     * @param string $cachename
+     * @param mixed $value
+     * @param int $expired
+     * @return boolean
+     */
+    protected function setCache($cachename, $value, $expired)
+    {
+        return Cache::set($cachename, $value, $expired);
+    }
+
+    /**
+     * 获取缓存,按需重载
+     * @param string $cachename
+     * @return mixed
+     */
+    protected function getCache($cachename)
+    {
+        return Cache::get($cachename);
+    }
+
+    /**
+     * 清除缓存,按需重载
+     * @param string $cachename
+     * @return boolean
+     */
+    protected function removeCache($cachename)
+    {
+        //TODO: remove cache implementation
+        return false;
+    }
+
+    /**
+     * 获取access_token
+     * @param string $appid 如在类初始化时已提供,则可为空
+     * @param string $appsecret 如在类初始化时已提供,则可为空
+     * @param string $token 手动指定access_token,非必要情况不建议用
+     */
+    public function checkAuth($appid = '', $appsecret = '', $token = '')
+    {
+        if (!$appid || !$appsecret) {
+            $appid     = $this->appid;
+            $appsecret = $this->appsecret;
+        }
+        if ($token) { //手动指定token,优先使用
+            $this->access_token = $token;
+            return $this->access_token;
+        }
+
+        $authname = 'wechat_access_token' . $appid;
+        /*if ($rs = $this->getCache($authname)) {
+            $this->access_token = $rs;
+            return $rs;
+        }*/
+        if ($rs = Db::name('weixin_accesstoken')
+            ->where('app_id',$appid)
+            ->where('expired_time','>',time())
+            ->order('create_time desc')
+            ->value('access_token')
+        ) {
+
+            $this->access_token = $rs;
+            return $rs;
+        }
+
+        $result = $this->http_get(self::API_URL_PREFIX . self::AUTH_URL . 'appid=' . $appid . '&secret=' . $appsecret);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            $this->access_token = $json['access_token'];
+            $expire             = $json['expires_in'] ? intval($json['expires_in']) - 100 : 3600;
+            //$this->setCache($authname, $this->access_token, $expire);
+            Db::name('weixin_accesstoken')->insert([
+                'app_id'       => $appid,
+                'expired_time' => $expire + time(),
+                'access_token' => $this->access_token,
+                'create_time'  => date('Y-m-d H:i:s'),
+            ]);
+            return $this->access_token;
+        }
+        return false;
+    }
+
+    /**
+     * 删除验证数据
+     * @param string $appid
+     */
+    public function resetAuth($appid = '')
+    {
+        if (!$appid) $appid = $this->appid;
+        $this->access_token = '';
+        $authname           = 'wechat_access_token' . $appid;
+        $this->removeCache($authname);
+        return true;
+    }
+
+    /**
+     * 删除JSAPI授权TICKET
+     * @param string $appid 用于多个appid时使用
+     */
+    public function resetJsTicket($appid = '')
+    {
+        if (!$appid) $appid = $this->appid;
+        $this->jsapi_ticket = '';
+        $authname           = 'wechat_jsapi_ticket' . $appid;
+        $this->removeCache($authname);
+        return true;
+    }
+
+    /**
+     * 获取JSAPI授权TICKET
+     * @param string $appid 用于多个appid时使用,可空
+     * @param string $jsapi_ticket 手动指定jsapi_ticket,非必要情况不建议用
+     */
+    public function getJsTicket($appid = '', $jsapi_ticket = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$appid) $appid = $this->appid;
+        if ($jsapi_ticket) { //手动指定token,优先使用
+            $this->jsapi_ticket = $jsapi_ticket;
+            return $this->jsapi_ticket;
+        }
+        $authname = 'wechat_jsapi_ticket' . $appid;
+        if ($rs = $this->getCache($authname)) {
+            $this->jsapi_ticket = $rs;
+            return $rs;
+        }
+        $result = $this->http_get(self::API_URL_PREFIX . self::GET_TICKET_URL . 'access_token=' . $this->access_token . '&type=jsapi');
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            $this->jsapi_ticket = $json['ticket'];
+            $expire             = $json['expires_in'] ? intval($json['expires_in']) - 100 : 3600;
+            $this->setCache($authname, $this->jsapi_ticket, $expire);
+            return $this->jsapi_ticket;
+        }
+        return false;
+    }
+
+
+    /**
+     * 获取JsApi使用签名
+     * @param string $url 网页的URL,自动处理#及其后面部分
+     * @param string $timestamp 当前时间戳 (为空则自动生成)
+     * @param string $noncestr 随机串 (为空则自动生成)
+     * @param string $appid 用于多个appid时使用,可空
+     * @return array|bool 返回签名字串
+     */
+    public function getJsSign($url, $timestamp = 0, $noncestr = '', $appid = '')
+    {
+        if (!$this->jsapi_ticket && !$this->getJsTicket($appid) || !$url) return false;
+        if (!$timestamp)
+            $timestamp = time();
+        if (!$noncestr)
+            $noncestr = $this->generateNonceStr();
+        $ret = strpos($url, '#');
+        if ($ret)
+            $url = substr($url, 0, $ret);
+        $url = trim($url);
+        if (empty($url))
+            return false;
+        $arrdata = ["timestamp" => $timestamp, "noncestr" => $noncestr, "url" => $url, "jsapi_ticket" => $this->jsapi_ticket];
+        $sign    = $this->getSignature($arrdata);
+        if (!$sign)
+            return false;
+        $signPackage = [
+            "appId"     => $this->appid,
+            "nonceStr"  => $noncestr,
+            "timestamp" => $timestamp,
+            "url"       => $url,
+            "signature" => $sign,
+        ];
+        return $signPackage;
+    }
+
+    /**
+     * 获取卡券签名cardSign
+     * @param string $card_type 卡券的类型,不可为空,官方jssdk文档说这个值可空,但签名验证工具又必填这个值,官方文档到处是坑,
+     * @param string $card_id 卡券的ID,可空
+     * @param string $location_id 卡券的适用门店ID,可空
+     * @param string $timestamp 当前时间戳 (为空则自动生成)
+     * @param string $noncestr 随机串 (为空则自动生成)
+     * @param string $appid 用于多个appid时使用,可空
+     * @return array|bool 返回签名字串
+     */
+    public function getCardSign($card_type = '', $card_id = '', $code = '', $location_id = '', $timestamp = 0, $noncestr = '', $appid = '')
+    {
+        if (!$this->api_ticket && !$this->getJsCardTicket($appid)) return false;
+        if (!$timestamp)
+            $timestamp = time();
+        if (!$noncestr)
+            $noncestr = $this->generateNonceStr();
+        $arrdata = ["api_ticket" => $this->api_ticket, "app_id" => $this->appid, "card_id" => $card_id, "code" => $code, "card_type" => $card_type, "location_id" => $location_id, "timestamp" => $timestamp, "noncestr" => $noncestr];
+        $sign    = $this->getTicketSignature($arrdata);
+        if (!$sign)
+            return false;
+        $signPackage = [
+            "cardType"  => $card_type,
+            "cardId"    => $card_id,
+            "shopId"    => $location_id,         //location_id就是shopId
+            "nonceStr"  => $noncestr,
+            "timestamp" => $timestamp,
+            "cardSign"  => $sign,
+        ];
+        return $signPackage;
+    }
+
+    /**
+     * 微信api不支持中文转义的json结构
+     * @param array $arr
+     */
+    static function json_encode($arr)
+    {
+        if (count($arr) == 0) return "[]";
+        $parts   = [];
+        $is_list = false;
+        //Find out if the given array is a numerical array
+        $keys       = array_keys($arr);
+        $max_length = count($arr) - 1;
+        if (($keys [0] === 0) && ($keys [$max_length] === $max_length)) { //See if the first key is 0 and last key is length - 1
+            $is_list = true;
+            for ($i = 0; $i < count($keys); $i++) { //See if each key correspondes to its position
+                if ($i != $keys [$i]) { //A key fails at position check.
+                    $is_list = false; //It is an associative array.
+                    break;
+                }
+            }
+        }
+        foreach ($arr as $key => $value) {
+            if (is_array($value)) { //Custom handling for arrays
+                if ($is_list)
+                    $parts [] = self::json_encode($value); /* :RECURSION: */
+                else
+                    $parts [] = '"' . $key . '":' . self::json_encode($value); /* :RECURSION: */
+            } else {
+                $str = '';
+                if (!$is_list)
+                    $str = '"' . $key . '":';
+                //Custom handling for multiple data types
+                if (!is_string($value) && is_numeric($value) && $value < 2000000000)
+                    $str .= $value; //Numbers
+                elseif ($value === false)
+                    $str .= 'false'; //The booleans
+                elseif ($value === true)
+                    $str .= 'true';
+                else
+                    $str .= '"' . addslashes($value) . '"'; //All other things
+                // :TODO: Is there any more datatype we should be in the lookout for? (Object?)
+                $parts [] = $str;
+            }
+        }
+        $json = implode(',', $parts);
+        if ($is_list)
+            return '[' . $json . ']'; //Return numerical JSON
+        return '{' . $json . '}'; //Return associative JSON
+    }
+
+    /**
+     * 获取签名
+     * @param array $arrdata 签名数组
+     * @param string $method 签名方法
+     * @return boolean|string 签名值
+     */
+    public function getSignature($arrdata, $method = "sha1")
+    {
+        if (!function_exists($method)) return false;
+        ksort($arrdata);
+        $paramstring = "";
+        foreach ($arrdata as $key => $value) {
+            if (strlen($paramstring) == 0)
+                $paramstring .= $key . "=" . $value;
+            else
+                $paramstring .= "&" . $key . "=" . $value;
+        }
+        $Sign = $method($paramstring);
+        return $Sign;
+    }
+
+    /**
+     * 获取微信卡券api_ticket
+     * @param string $appid 用于多个appid时使用,可空
+     * @param string $api_ticket 手动指定api_ticket,非必要情况不建议用
+     */
+    public function getJsCardTicket($appid = '', $api_ticket = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$appid) $appid = $this->appid;
+        if ($api_ticket) { //手动指定token,优先使用
+            $this->api_ticket = $api_ticket;
+            return $this->api_ticket;
+        }
+        $authname = 'wechat_api_ticket_wxcard' . $appid;
+        if ($rs = $this->getCache($authname)) {
+            $this->api_ticket = $rs;
+            return $rs;
+        }
+        $result = $this->http_get(self::API_URL_PREFIX . self::GET_TICKET_URL . 'access_token=' . $this->access_token . '&type=wx_card');
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            $this->api_ticket = $json['ticket'];
+            $expire           = $json['expires_in'] ? intval($json['expires_in']) - 100 : 3600;
+            $this->setCache($authname, $this->api_ticket, $expire);
+            return $this->api_ticket;
+        }
+        return false;
+    }
+
+    /**
+     * 获取微信卡券签名
+     * @param array $arrdata 签名数组
+     * @param string $method 签名方法
+     * @return boolean|string 签名值
+     */
+    public function getTicketSignature($arrdata, $method = "sha1")
+    {
+        if (!function_exists($method)) return false;
+        $newArray = [];
+        foreach ($arrdata as $key => $value) {
+            array_push($newArray, (string)$value);
+        }
+        sort($newArray, SORT_STRING);
+        return $method(implode($newArray));
+    }
+
+    /**
+     * 生成随机字串
+     * @param number $length 长度,默认为16,最长为32字节
+     * @return string
+     */
+    public function generateNonceStr($length = 16)
+    {
+        // 密码字符集,可任意添加你需要的字符
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        $str   = "";
+        for ($i = 0; $i < $length; $i++) {
+            $str .= $chars[mt_rand(0, strlen($chars) - 1)];
+        }
+        return $str;
+    }
+
+    /**
+     * 获取微信服务器IP地址列表
+     * @return array('127.0.0.1','127.0.0.1')
+     */
+    public function getServerIp()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::CALLBACKSERVER_GET_URL . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json['ip_list'];
+        }
+        return false;
+    }
+
+    /**
+     * 创建菜单(认证后的订阅号可用)
+     * @param array $data 菜单数组数据
+     * example:
+     *    array (
+     *        'button' => array (
+     *          0 => array (
+     *            'name' => '扫码',
+     *            'sub_button' => array (
+     *                0 => array (
+     *                  'type' => 'scancode_waitmsg',
+     *                  'name' => '扫码带提示',
+     *                  'key' => 'rselfmenu_0_0',
+     *                ),
+     *                1 => array (
+     *                  'type' => 'scancode_push',
+     *                  'name' => '扫码推事件',
+     *                  'key' => 'rselfmenu_0_1',
+     *                ),
+     *            ),
+     *          ),
+     *          1 => array (
+     *            'name' => '发图',
+     *            'sub_button' => array (
+     *                0 => array (
+     *                  'type' => 'pic_sysphoto',
+     *                  'name' => '系统拍照发图',
+     *                  'key' => 'rselfmenu_1_0',
+     *                ),
+     *                1 => array (
+     *                  'type' => 'pic_photo_or_album',
+     *                  'name' => '拍照或者相册发图',
+     *                  'key' => 'rselfmenu_1_1',
+     *                )
+     *            ),
+     *          ),
+     *          2 => array (
+     *            'type' => 'location_select',
+     *            'name' => '发送位置',
+     *            'key' => 'rselfmenu_2_0'
+     *          ),
+     *        ),
+     *    )
+     * type可以选择为以下几种,其中5-8除了收到菜单事件以外,还会单独收到对应类型的信息。
+     * 1、click:点击推事件
+     * 2、view:跳转URL
+     * 3、scancode_push:扫码推事件
+     * 4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框
+     * 5、pic_sysphoto:弹出系统拍照发图
+     * 6、pic_photo_or_album:弹出拍照或者相册发图
+     * 7、pic_weixin:弹出微信相册发图器
+     * 8、location_select:弹出地理位置选择器
+     */
+    public function createMenu($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MENU_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 获取菜单(认证后的订阅号可用)
+     * @return array('menu'=>array(....s))
+     */
+    public function getMenu()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::MENU_GET_URL . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 删除菜单(认证后的订阅号可用)
+     * @return boolean
+     */
+    public function deleteMenu()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::MENU_DELETE_URL . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 创建个性化菜单(认证后的订阅号可用)
+     * @param array $data
+     * @return bool
+     *
+     */
+    public function addconditionalMenu($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MENU_ADDCONDITIONAL_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 删除个性化菜单(认证后的订阅号可用)
+     * @param $data {"menuid":"208379533"}
+     *
+     * @return bool
+     */
+    public function delconditionalMenu($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MENU_DELCONDITIONAL_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 测试个性化菜单匹配结果(认证后的订阅号可用)
+     * @param $data {"user_id":"weixin"} user_id可以是粉丝的OpenID,也可以是粉丝的微信号
+     *
+     * @return bool|array('button'=>array(....s))
+     */
+    public function trymatchMenu($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MENU_TRYMATCH_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传临时素材,有效期为3天(认证后的订阅号可用)
+     * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * 注意:临时素材的media_id是可复用的!
+     * @param array $data {"media":'@Path\filename.jpg'}
+     * @param type 类型:图片:image 语音:voice 视频:video 缩略图:thumb
+     * @return boolean|array
+     */
+    public function uploadMedia($data, $type)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_UPLOAD_URL . 'access_token=' . $this->access_token . '&type=' . $type, $data, true);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取临时素材(认证后的订阅号可用)
+     * @param string $media_id 媒体文件id
+     * @param boolean $is_video 是否为视频文件,默认为否
+     * @return raw data
+     */
+    public function getMedia($media_id, $is_video = false)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+        //如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+        $url_prefix = $is_video ? str_replace('https', 'http', self::API_URL_PREFIX) : self::API_URL_PREFIX;
+        $result     = $this->http_get($url_prefix . self::MEDIA_GET_URL . 'access_token=' . $this->access_token . '&media_id=' . $media_id);
+        if ($result) {
+            if (is_string($result)) {
+                $json = json_decode($result, true);
+                if (isset($json['errcode'])) {
+                    $this->errCode = $json['errcode'];
+                    $this->errMsg  = $json['errmsg'];
+                    return false;
+                }
+            }
+            return $result;
+        }
+        return false;
+    }
+
+    /**
+     * 上传图片,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。 (认证后的订阅号可用)
+     * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * @param array $data {"media":'@Path\filename.jpg'}
+     *
+     * @return boolean|array
+     */
+    public function uploadImg($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //原先的上传多媒体文件接口使用 self::UPLOAD_MEDIA_URL 前缀
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_UPLOADIMG_URL . 'access_token=' . $this->access_token, $data, true);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+
+    /**
+     * 上传永久素材(认证后的订阅号可用)
+     * 新增的永久素材也可以在公众平台官网素材管理模块中看到
+     * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * @param array $data {"media":'@Path\filename.jpg'}
+     * @param type 类型:图片:image 语音:voice 视频:video 缩略图:thumb
+     * @param boolean $is_video 是否为视频文件,默认为否
+     * @param array $video_info 视频信息数组,非视频素材不需要提供 array('title'=>'视频标题','introduction'=>'描述')
+     * @return boolean|array
+     */
+    public function uploadForeverMedia($data, $type, $is_video = false, $video_info = [])
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //#TODO 暂不确定此接口是否需要让视频文件走http协议
+        //如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+        //$url_prefix = $is_video?str_replace('https','http',self::API_URL_PREFIX):self::API_URL_PREFIX;
+        //当上传视频文件时,附加视频文件信息
+        if ($is_video) $data['description'] = self::json_encode($video_info);
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_UPLOAD_URL . 'access_token=' . $this->access_token . '&type=' . $type, $data, true);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传永久图文素材(认证后的订阅号可用)
+     * 新增的永久素材也可以在公众平台官网素材管理模块中看到
+     * @param array $data 消息结构{"articles":[{...}]}
+     * @return boolean|array
+     */
+    public function uploadForeverArticles($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_NEWS_UPLOAD_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 修改永久图文素材(认证后的订阅号可用)
+     * 永久素材也可以在公众平台官网素材管理模块中看到
+     * @param string $media_id 图文素材id
+     * @param array $data 消息结构{"articles":[{...}]}
+     * @param int $index 更新的文章在图文素材的位置,第一篇为0,仅多图文使用
+     * @return boolean|array
+     */
+    public function updateForeverArticles($media_id, $data, $index = 0)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!isset($data['media_id'])) $data['media_id'] = $media_id;
+        if (!isset($data['index'])) $data['index'] = $index;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_NEWS_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取永久素材(认证后的订阅号可用)
+     * 返回图文消息数组或二进制数据,失败返回false
+     * @param string $media_id 媒体文件id
+     * @param boolean $is_video 是否为视频文件,默认为否
+     * @return boolean|array|raw data
+     */
+    public function getForeverMedia($media_id, $is_video = false)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = ['media_id' => $media_id];
+        //#TODO 暂不确定此接口是否需要让视频文件走http协议
+        //如果要获取的素材是视频文件时,不能使用https协议,必须更换成http协议
+        //$url_prefix = $is_video?str_replace('https','http',self::API_URL_PREFIX):self::API_URL_PREFIX;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_GET_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            if (is_string($result)) {
+                $json = json_decode($result, true);
+                if ($json) {
+                    if (isset($json['errcode'])) {
+                        $this->errCode = $json['errcode'];
+                        $this->errMsg  = $json['errmsg'];
+                        return false;
+                    }
+                    return $json;
+                } else {
+                    return $result;
+                }
+            }
+            return $result;
+        }
+        return false;
+    }
+
+    /**
+     * 删除永久素材(认证后的订阅号可用)
+     * @param string $media_id 媒体文件id
+     * @return boolean
+     */
+    public function delForeverMedia($media_id)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = ['media_id' => $media_id];
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_DEL_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 获取永久素材列表(认证后的订阅号可用)
+     * @param string $type 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)
+     * @param int $offset 全部素材的偏移位置,0表示从第一个素材
+     * @param int $count 返回素材的数量,取值在1到20之间
+     * @return boolean|array
+     * 返回数组格式:
+     * array(
+     *  'total_count'=>0, //该类型的素材的总数
+     *  'item_count'=>0,  //本次调用获取的素材的数量
+     *  'item'=>array()   //素材列表数组,内容定义请参考官方文档
+     * )
+     */
+    public function getForeverList($type, $offset, $count)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'type'   => $type,
+            'offset' => $offset,
+            'count'  => $count,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_FOREVER_BATCHGET_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取永久素材总数(认证后的订阅号可用)
+     * @return boolean|array
+     * 返回数组格式:
+     * array(
+     *  'voice_count'=>0, //语音总数量
+     *  'video_count'=>0, //视频总数量
+     *  'image_count'=>0, //图片总数量
+     *  'news_count'=>0   //图文总数量
+     * )
+     */
+    public function getForeverCount()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::MEDIA_FOREVER_COUNT_URL . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传图文消息素材,用于群发(认证后的订阅号可用)
+     * @param array $data 消息结构{"articles":[{...}]}
+     * @return boolean|array
+     */
+    public function uploadArticles($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MEDIA_UPLOADNEWS_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传视频素材(认证后的订阅号可用)
+     * @param array $data 消息结构
+     * {
+     *     "media_id"=>"",     //通过上传媒体接口得到的MediaId
+     *     "title"=>"TITLE",    //视频标题
+     *     "description"=>"Description"        //视频描述
+     * }
+     * @return boolean|array
+     * {
+     *     "type":"video",
+     *     "media_id":"mediaid",
+     *     "created_at":1398848981
+     *  }
+     */
+    public function uploadMpVideo($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::UPLOAD_MEDIA_URL . self::MEDIA_VIDEO_UPLOAD . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 高级群发消息, 根据OpenID列表群发图文消息(订阅号不可用)
+     *    注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+     *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+     * @param array $data 消息结构
+     * {
+     *     "touser"=>array(
+     *         "OPENID1",
+     *         "OPENID2"
+     *     ),
+     *      "msgtype"=>"mpvideo",
+     *      // 在下面5种类型中选择对应的参数内容
+     *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+     *      // text => array ( "content" => "hello")
+     * }
+     * @return boolean|array
+     */
+    public function sendMassMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MASS_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 高级群发消息, 根据群组id群发图文消息(认证后的订阅号可用)
+     *    注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+     *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+     * @param array $data 消息结构
+     * {
+     *     "filter"=>array(
+     *         "is_to_all"=>False,     //是否群发给所有用户.True不用分组id,False需填写分组id
+     *         "group_id"=>"2"     //群发的分组id
+     *     ),
+     *      "msgtype"=>"mpvideo",
+     *      // 在下面5种类型中选择对应的参数内容
+     *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+     *      // text => array ( "content" => "hello")
+     * }
+     * @return boolean|array
+     */
+    public function sendGroupMassMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MASS_SEND_GROUP_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 高级群发消息, 删除群发图文消息(认证后的订阅号可用)
+     * @param int $msg_id 消息id
+     * @return boolean|array
+     */
+    public function deleteMassMessage($msg_id)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MASS_DELETE_URL . 'access_token=' . $this->access_token, self::json_encode(['msg_id' => $msg_id]));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 高级群发消息, 预览群发消息(认证后的订阅号可用)
+     *    注意:视频需要在调用uploadMedia()方法后,再使用 uploadMpVideo() 方法生成,
+     *             然后获得的 mediaid 才能用于群发,且消息类型为 mpvideo 类型。
+     * @param array $data 消息结构
+     * {
+     *     "touser"=>"OPENID",
+     *      "msgtype"=>"mpvideo",
+     *      // 在下面5种类型中选择对应的参数内容
+     *      // mpnews | voice | image | mpvideo => array( "media_id"=>"MediaId")
+     *      // text => array ( "content" => "hello")
+     * }
+     * @return boolean|array
+     */
+    public function previewMassMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MASS_PREVIEW_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 高级群发消息, 查询群发消息发送状态(认证后的订阅号可用)
+     * @param int $msg_id 消息id
+     * @return boolean|array
+     * {
+     *     "msg_id":201053012,     //群发消息后返回的消息id
+     *     "msg_status":"SEND_SUCCESS" //消息发送后的状态,SENDING表示正在发送 SEND_SUCCESS表示发送成功
+     * }
+     */
+    public function queryMassMessage($msg_id)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::MASS_QUERY_URL . 'access_token=' . $this->access_token, self::json_encode(['msg_id' => $msg_id]));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 创建二维码ticket
+     * @param int|string $scene_id 自定义追踪id,临时二维码只能用数值型
+     * @param int $type 0:临时二维码;1:数值型永久二维码(此时expire参数无效);2:字符串型永久二维码(此时expire参数无效  3:临时二维码(字符型);)
+     * @param int $expire 临时二维码有效期,最大为604800秒
+     * @return array('ticket'=>'qrcode字串','expire_seconds'=>2592000,'url'=>'二维码图片解析后的地址')
+     */
+    public function getQRCode($scene_id, $type = 0, $expire = 2592000)
+    {
+        if (!$this->access_token && !$this->checkAuth()) {
+            return false;
+        }
+        if (!isset($scene_id)) {
+            return false;
+        }
+        switch ($type) {
+            case '0':
+                if (!is_numeric($scene_id))
+                    return false;
+                $action_name = 'QR_SCENE';
+                $action_info = ['scene' => (['scene_id' => $scene_id])];
+                break;
+
+            case '3':
+                if (!is_string($scene_id))
+                    return false;
+                $action_name = 'QR_STR_SCENE';
+                $action_info = ['scene' => (['scene_str' => $scene_id])];
+                break;
+
+            case '1':
+                if (!is_numeric($scene_id))
+                    return false;
+                $action_name = 'QR_LIMIT_SCENE';
+                $action_info = ['scene' => (['scene_id' => $scene_id])];
+                break;
+
+            case '2':
+                if (!is_string($scene_id))
+                    return false;
+                $action_name = 'QR_LIMIT_STR_SCENE';
+                $action_info = ['scene' => (['scene_str' => $scene_id])];
+                break;
+
+            default:
+                return false;
+        }
+        $data = [
+            'action_name'    => $action_name,
+            'expire_seconds' => $expire,
+            'action_info'    => $action_info,
+        ];
+        if ($type == '1' || $type == '2') {
+            unset($data['expire_seconds']);
+        }
+        $result = $this->http_post(self::API_URL_PREFIX . self::QRCODE_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取二维码图片
+     * @param string $ticket 传入由getQRCode方法生成的ticket参数
+     * @return string url 返回http地址
+     */
+    public function getQRUrl($ticket)
+    {
+        return self::QRCODE_IMG_URL . urlencode($ticket);
+    }
+
+    /**
+     * 长链接转短链接接口
+     * @param string $long_url 传入要转换的长url
+     * @return boolean|string url 成功则返回转换后的短url
+     */
+    public function getShortUrl($long_url)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'action'   => 'long2short',
+            'long_url' => $long_url,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::SHORT_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json['short_url'];
+        }
+        return false;
+    }
+
+    /**
+     * 获取统计数据
+     * @param string $type 数据分类(user|article|upstreammsg|interface)分别为(用户分析|图文分析|消息分析|接口分析)
+     * @param string $subtype 数据子分类,参考 DATACUBE_URL_ARR 常量定义部分 或者README.md说明文档
+     * @param string $begin_date 开始时间
+     * @param string $end_date 结束时间
+     * @return boolean|array 成功返回查询结果数组,其定义请看官方文档
+     */
+    public function getDatacube($type, $subtype, $begin_date, $end_date = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!isset(self::$DATACUBE_URL_ARR[$type]) || !isset(self::$DATACUBE_URL_ARR[$type][$subtype]))
+            return false;
+        $data   = [
+            'begin_date' => $begin_date,
+            'end_date'   => $end_date ? $end_date : $begin_date,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::$DATACUBE_URL_ARR[$type][$subtype] . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return isset($json['list']) ? $json['list'] : $json;
+        }
+        return false;
+    }
+
+    /**
+     * 批量获取关注用户列表
+     * @param unknown $next_openid
+     */
+    public function getUserList($next_openid = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::USER_GET_URL . 'access_token=' . $this->access_token . '&next_openid=' . $next_openid);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取关注者详细信息
+     * @param string $openid
+     * @param string $lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
+     * @return array {subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]}
+     * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+     */
+    public function getUserInfo($openid, $lang = 'zh_CN')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::USER_INFO_URL . 'access_token=' . $this->access_token . '&openid=' . $openid . '&lang=' . $lang);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 批量获取关注者详细信息
+     * @param array $openids user_list{{'openid:xxxxxx'},{},{}}
+     * @return array user_info_list{subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]}{}{}...
+     * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+     */
+    public function getUsersInfo($openids)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::USERS_INFO_URL . 'access_token=' . $this->access_token, json_encode($openids));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 设置用户备注名
+     * @param string $openid
+     * @param string $remark 备注名
+     * @return boolean|array
+     */
+    public function updateUserRemark($openid, $remark)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'openid' => $openid,
+            'remark' => $remark,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::USER_UPDATEREMARK_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取用户分组列表
+     * @return boolean|array
+     */
+    public function getGroup()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::GROUP_GET_URL . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取用户所在分组
+     * @param string $openid
+     * @return boolean|int 成功则返回用户分组id
+     */
+    public function getUserGroup($openid)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'openid' => $openid,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::USER_GROUP_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            } else
+                if (isset($json['groupid'])) return $json['groupid'];
+        }
+        return false;
+    }
+
+    /**
+     * 新增自定分组
+     * @param string $name 分组名称
+     * @return boolean|array
+     */
+    public function createGroup($name)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'group' => ['name' => $name],
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_CREATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 更改分组名称
+     * @param int $groupid 分组id
+     * @param string $name 分组名称
+     * @return boolean|array
+     */
+    public function updateGroup($groupid, $name)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'group' => ['id' => $groupid, 'name' => $name],
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 移动用户分组
+     * @param int $groupid 分组id
+     * @param string $openid 用户openid
+     * @return boolean|array
+     */
+    public function updateGroupMembers($groupid, $openid)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'openid'     => $openid,
+            'to_groupid' => $groupid,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_MEMBER_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 批量移动用户分组
+     * @param int $groupid 分组id
+     * @param string $openid_list 用户openid数组,一次不能超过50个
+     * @return boolean|array
+     */
+    public function batchUpdateGroupMembers($groupid, $openid_list)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'openid_list' => $openid_list,
+            'to_groupid'  => $groupid,
+        ];
+        $result = $this->http_post(self::API_URL_PREFIX . self::GROUP_MEMBER_BATCHUPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 发送客服消息
+     * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}}
+     * @return boolean|array
+     */
+    public function sendCustomMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //$result = $this->http_post(self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        return Queue::instance()->send(Queue::KEY_WX_CUSTOMER_MESSAGE, [
+            'url'     => self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->access_token,
+            'message' => $data,
+        ]);
+    }
+
+    /**
+     * 直接发送
+     * @param $data
+     * @return bool|mixed
+     */
+    public function sendCustomMessageDirect($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * oauth 授权跳转接口
+     * @param string $callback 回调URI
+     * @return string
+     */
+    public function getOauthRedirect($callback, $state = '', $scope = 'snsapi_userinfo')
+    {
+        return self::OAUTH_PREFIX . self::OAUTH_AUTHORIZE_URL . 'appid=' . $this->appid . '&redirect_uri=' . urlencode($callback) . '&response_type=code&scope=' . $scope . '&state=' . $state . '#wechat_redirect';
+    }
+
+    /**
+     * 通过code获取Access Token
+     * @return array {access_token,expires_in,refresh_token,openid,scope}
+     */
+    public function getOauthAccessToken()
+    {
+        $code = isset($_GET['code']) ? $_GET['code'] : '';
+        if (!$code) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::OAUTH_TOKEN_URL . 'appid=' . $this->appid . '&secret=' . $this->appsecret . '&code=' . $code . '&grant_type=authorization_code');
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            $this->user_token = $json['access_token'];
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 刷新access token并续期
+     * @param string $refresh_token
+     * @return boolean|mixed
+     */
+    public function getOauthRefreshToken($refresh_token)
+    {
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::OAUTH_REFRESH_URL . 'appid=' . $this->appid . '&grant_type=refresh_token&refresh_token=' . $refresh_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            $this->user_token = $json['access_token'];
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取授权后的用户资料
+     * @param string $access_token
+     * @param string $openid
+     * @return array {openid,nickname,sex,province,city,country,headimgurl,privilege,[unionid]}
+     * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+     */
+    public function getOauthUserinfo($access_token, $openid)
+    {
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::OAUTH_USERINFO_URL . 'access_token=' . $access_token . '&openid=' . $openid);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 检验授权凭证是否有效
+     * @param string $access_token
+     * @param string $openid
+     * @return boolean 是否有效
+     */
+    public function getOauthAuth($access_token, $openid)
+    {
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::OAUTH_AUTH_URL . 'access_token=' . $access_token . '&openid=' . $openid);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            } else
+                if ($json['errcode'] == 0) return true;
+        }
+        return false;
+    }
+
+    /**
+     * 模板消息 设置所属行业
+     * @param int $id1 公众号模板消息所属行业编号,参看官方开发文档 行业代码
+     * @param int $id2 同$id1。但如果只有一个行业,此参数可省略
+     * @return boolean|array
+     */
+    public function setTMIndustry($id1, $id2 = '')
+    {
+        if ($id1) $data['industry_id1'] = $id1;
+        if ($id2) $data['industry_id2'] = $id2;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::TEMPLATE_SET_INDUSTRY_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 模板消息 添加消息模板
+     * 成功返回消息模板的调用id
+     * @param string $tpl_id 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式
+     * @return boolean|string
+     */
+    public function addTemplateMessage($tpl_id)
+    {
+        $data = ['template_id_short' => $tpl_id];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::TEMPLATE_ADD_TPL_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json['template_id'];
+        }
+        return false;
+    }
+
+    /**
+     * 发送模板消息
+     * @param array $data 消息结构
+     * {
+     * "touser":"OPENID",
+     * "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
+     * "url":"http://weixin.qq.com/download",
+     * "topcolor":"#FF0000",
+     * "data":{
+     * "参数名1": {
+     * "value":"参数",
+     * "color":"#173177"     //参数颜色
+     * },
+     * "Date":{
+     * "value":"06月07日 19时24分",
+     * "color":"#173177"
+     * },
+     * "CardNumber":{
+     * "value":"0426",
+     * "color":"#173177"
+     * },
+     * "Type":{
+     * "value":"消费",
+     * "color":"#173177"
+     * }
+     * }
+     * }
+     * @return boolean|array
+     */
+    public function sendTemplateMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        //$result = $this->http_post(self::API_URL_PREFIX . self::CUSTOM_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        return Queue::instance()->send(Queue::KEY_WX_CUSTOMER_MESSAGE, [
+            'url'     => self::API_URL_PREFIX . self::TEMPLATE_SEND_URL . 'access_token=' . $this->access_token,
+            'message' => $data,
+        ]);
+    }
+
+    /**
+     * 发送模板消息
+     * @param $data
+     */
+    public function sendTemplateMessageDirect($data) {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::TEMPLATE_SEND_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+
+    /**
+     * 获取多客服会话记录
+     * @param array $data 数据结构{"starttime":123456789,"endtime":987654321,"openid":"OPENID","pagesize":10,"pageindex":1,}
+     * @return boolean|array
+     */
+    public function getCustomServiceMessage($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_RECORD . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 转发多客服消息
+     * Example: $obj->transfer_customer_service($customer_account)->reply();
+     * @param string $customer_account 转发到指定客服帐号:test1@test
+     */
+    public function transfer_customer_service($customer_account = '')
+    {
+        $msg = [
+            'ToUserName'   => $this->getRevFrom(),
+            'FromUserName' => $this->getRevTo(),
+            'CreateTime'   => time(),
+            'MsgType'      => 'transfer_customer_service',
+        ];
+        if ($customer_account) {
+            $msg['TransInfo'] = ['KfAccount' => $customer_account];
+        }
+        $this->Message($msg);
+        return $this;
+    }
+
+    /**
+     * 获取多客服客服基本信息
+     *
+     * @return boolean|array
+     */
+    public function getCustomServiceKFlist()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_KFLIST . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取多客服在线客服接待信息
+     *
+     * @return boolean|array {
+     * "kf_online_list": [
+     * {
+     * "kf_account": "test1@test",    //客服账号@微信别名
+     * "status": 1,            //客服在线状态 1:pc在线,2:手机在线,若pc和手机同时在线则为 1+2=3
+     * "kf_id": "1001",        //客服工号
+     * "auto_accept": 0,        //客服设置的最大自动接入数
+     * "accepted_case": 1        //客服当前正在接待的会话数
+     * }
+     * ]
+     * }
+     */
+    public function getCustomServiceOnlineKFlist()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_URL_PREFIX . self::CUSTOM_SERVICE_GET_ONLINEKFLIST . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 创建指定多客服会话
+     * @tutorial 当用户已被其他客服接待或指定客服不在线则会失败
+     * @param string $openid //用户openid
+     * @param string $kf_account //客服账号
+     * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空
+     * @return boolean | array            //成功返回json数组
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function createKFSession($openid, $kf_account, $text = '')
+    {
+        $data = [
+            "openid"     => $openid,
+            "kf_account" => $kf_account,
+        ];
+        if ($text) $data["text"] = $text;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_CREATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 关闭指定多客服会话
+     * @tutorial 当用户被其他客服接待时则会失败
+     * @param string $openid //用户openid
+     * @param string $kf_account //客服账号
+     * @param string $text //附加信息,文本会展示在客服人员的多客服客户端,可为空
+     * @return boolean | array            //成功返回json数组
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function closeKFSession($openid, $kf_account, $text = '')
+    {
+        $data = [
+            "openid"     => $openid,
+            "kf_account" => $kf_account,
+        ];
+        if ($text) $data["text"] = $text;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_CLOSE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取用户会话状态
+     * @param string $openid //用户openid
+     * @return boolean | array            //成功返回json数组
+     * {
+     *     "errcode" : 0,
+     *     "errmsg" : "ok",
+     *     "kf_account" : "test1@test",    //正在接待的客服
+     *     "createtime": 123456789,        //会话接入时间
+     *  }
+     */
+    public function getKFSession($openid)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET . 'access_token=' . $this->access_token . '&openid=' . $openid);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取指定客服的会话列表
+     * @param string $openid //用户openid
+     * @return boolean | array            //成功返回json数组
+     *  array(
+     *     'sessionlist' => array (
+     *         array (
+     *             'openid'=>'OPENID',             //客户 openid
+     *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+     *         ),
+     *         array (
+     *             'openid'=>'OPENID',             //客户 openid
+     *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+     *         ),
+     *     )
+     *  )
+     */
+    public function getKFSessionlist($kf_account)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET_LIST . 'access_token=' . $this->access_token . '&kf_account=' . $kf_account);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取未接入会话列表
+     * @param string $openid //用户openid
+     * @return boolean | array            //成功返回json数组
+     *  array (
+     *     'count' => 150 ,                            //未接入会话数量
+     *     'waitcaselist' => array (
+     *         array (
+     *             'openid'=>'OPENID',             //客户 openid
+     *             'kf_account ' =>'',                   //指定接待的客服,为空则未指定
+     *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+     *         ),
+     *         array (
+     *             'openid'=>'OPENID',             //客户 openid
+     *             'kf_account ' =>'',                   //指定接待的客服,为空则未指定
+     *             'createtime'=>123456789,  //会话创建时间,UNIX 时间戳
+     *         )
+     *     )
+     *  )
+     */
+    public function getKFSessionWait()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CUSTOM_SESSION_GET_WAIT . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 添加客服账号
+     *
+     * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+     * @param string $nickname //客服昵称,最长6个汉字或12个英文字符
+     * @param string $password //客服账号明文登录密码,会自动加密
+     * @return boolean|array
+     * 成功返回结果
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function addKFAccount($account, $nickname, $password)
+    {
+        $data = [
+            "kf_account" => $account,
+            "nickname"   => $nickname,
+            "password"   => md5($password),
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_ADD_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 修改客服账号信息
+     *
+     * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+     * @param string $nickname //客服昵称,最长6个汉字或12个英文字符
+     * @param string $password //客服账号明文登录密码,会自动加密
+     * @return boolean|array
+     * 成功返回结果
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function updateKFAccount($account, $nickname, $password)
+    {
+        $data = [
+            "kf_account" => $account,
+            "nickname"   => $nickname,
+            "password"   => md5($password),
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_UPDATE_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 删除客服账号
+     *
+     * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+     * @return boolean|array
+     * 成功返回结果
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function deleteKFAccount($account)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_DEL_URL . 'access_token=' . $this->access_token . '&kf_account=' . $account);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传客服头像
+     *
+     * @param string $account //完整客服账号,格式为:账号前缀@公众号微信号,账号前缀最多10个字符,必须是英文或者数字字符
+     * @param string $imgfile //头像文件完整路径,如:'D:\user.jpg'。头像文件必须JPG格式,像素建议640*640
+     * @return boolean|array
+     * 成功返回结果
+     * {
+     *   "errcode": 0,
+     *   "errmsg": "ok",
+     * }
+     */
+    public function setKFHeadImg($account, $imgfile)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL . 'access_token=' . $this->access_token . '&kf_account=' . $account, ['media' => '@' . $imgfile], true);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 语义理解接口
+     * @param String $uid 用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid)
+     * @param String $query 输入文本串
+     * @param String $category 需要使用的服务类型,多个用“,”隔开,不能为空
+     * @param Float $latitude 纬度坐标,与经度同时传入;与城市二选一传入
+     * @param Float $longitude 经度坐标,与纬度同时传入;与城市二选一传入
+     * @param String $city 城市名称,与经纬度二选一传入
+     * @param String $region 区域名称,在城市存在的情况下可省略;与经纬度二选一传入
+     * @return boolean|array
+     */
+    public function querySemantic($uid, $query, $category, $latitude = 0, $longitude = 0, $city = "", $region = "")
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data = [
+            'query'    => $query,
+            'category' => $category,
+            'appid'    => $this->appid,
+            'uid'      => '',
+        ];
+        //地理坐标或城市名称二选一
+        if ($latitude) {
+            $data['latitude']  = $latitude;
+            $data['longitude'] = $longitude;
+        } elseif ($city) {
+            $data['city'] = $city;
+        } elseif ($region) {
+            $data['region'] = $region;
+        }
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SEMANTIC_API_URL . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 创建卡券
+     * @param Array $data 卡券数据
+     * @return array|boolean 返回数组中card_id为卡券ID
+     */
+    public function createCard($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CREATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 更改卡券信息
+     * 调用该接口更新信息后会重新送审,卡券状态变更为待审核。已被用户领取的卡券会实时更新票面信息。
+     * @param string $data
+     * @return boolean
+     */
+    public function updateCard($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 删除卡券
+     * 允许商户删除任意一类卡券。删除卡券后,该卡券对应已生成的领取用二维码、添加到卡包 JS API 均会失效。
+     * 注意:删除卡券不能删除已被用户领取,保存在微信客户端中的卡券,已领取的卡券依旧有效。
+     * @param string $card_id 卡券ID
+     * @return boolean
+     */
+    public function delCard($card_id)
+    {
+        $data = [
+            'card_id' => $card_id,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_DELETE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 查询卡券详情
+     * @param string $card_id
+     * @return boolean|array    返回数组信息比较复杂,请参看卡券接口文档
+     */
+    public function getCardInfo($card_id)
+    {
+        $data = [
+            'card_id' => $card_id,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_GET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取用户已领取卡券接口
+     * @param string $openid
+     * @param string $card_id
+     * @return boolean|array    返回数组信息比较复杂,请参看卡券接口文档
+     * 成功返回结果
+     *  {
+     * "errcode":0,
+     * "errmsg":"ok",
+     * "card_list": [
+     * {"code": "xxx1434079154", "card_id": "xxxxxxxxxx"},
+     * {"code": "xxx1434079155", "card_id": "xxxxxxxxxx"}
+     * ]
+     * }
+     */
+    public function getUserCardList($openid, $card_id)
+    {
+        $data = [
+            'openid'  => $openid,
+            'card_id' => $card_id,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_USER_GETCARDLIST . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 获取颜色列表
+     * 获得卡券的最新颜色列表,用于创建卡券
+     * @return boolean|array   返回数组请参看 微信卡券接口文档 的json格式
+     */
+    public function getCardColors()
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_get(self::API_BASE_URL_PREFIX . self::CARD_GETCOLORS . 'access_token=' . $this->access_token);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 拉取门店列表
+     * 获取在公众平台上申请创建的门店列表
+     * @param int $offset 开始拉取的偏移,默认为0从头开始
+     * @param int $count 拉取的数量,默认为0拉取全部
+     * @return boolean|array   返回数组请参看 微信卡券接口文档 的json格式
+     */
+    public function getCardLocations($offset = 0, $count = 0)
+    {
+        $data = [
+            'offset' => $offset,
+            'count'  => $count,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 批量导入门店信息
+     * @tutorial 返回插入的门店id列表,以逗号分隔。如果有插入失败的,则为-1,请自行核查是哪个插入失败
+     * @param array $data 数组形式的json数据,由于内容较多,具体内容格式请查看 微信卡券接口文档
+     * @return boolean|string 成功返回插入的门店id列表
+     */
+    public function addCardLocations($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LOCATION_BATCHADD . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 生成卡券二维码
+     * 成功则直接返回ticket值,可以用 getQRUrl($ticket) 换取二维码url
+     *
+     * @param string $cardid 卡券ID 必须
+     * @param string $code 指定卡券 code 码,只能被领一次。use_custom_code 字段为 true 的卡券必须填写,非自定义 code 不必填写。
+     * @param string $openid 指定领取者的 openid,只有该用户能领取。bind_openid 字段为 true 的卡券必须填写,非自定义 openid 不必填写。
+     * @param int $expire_seconds 指定二维码的有效时间,范围是 60 ~ 1800 秒。不填默认为永久有效。
+     * @param boolean $is_unique_code 指定下发二维码,生成的二维码随机分配一个 code,领取后不可再次扫描。填写 true 或 false。默认 false。
+     * @param string $balance 红包余额,以分为单位。红包类型必填(LUCKY_MONEY),其他卡券类型不填。
+     * @return boolean|string
+     */
+    public function createCardQrcode($card_id, $code = '', $openid = '', $expire_seconds = 0, $is_unique_code = false, $balance = '')
+    {
+        $card = [
+            'card_id' => $card_id,
+        ];
+        $data = [
+            'action_name' => "QR_CARD",
+        ];
+        if ($code)
+            $card['code'] = $code;
+        if ($openid)
+            $card['openid'] = $openid;
+        if ($is_unique_code)
+            $card['is_unique_code'] = $is_unique_code;
+        if ($balance)
+            $card['balance'] = $balance;
+        if ($expire_seconds)
+            $data['expire_seconds'] = $expire_seconds;
+        $data['action_info'] = ['card' => $card];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_QRCODE_CREATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 消耗 code
+     * 自定义 code(use_custom_code 为 true)的优惠券,在 code 被核销时,必须调用此接口。
+     *
+     * @param string $code 要消耗的序列号
+     * @param string $card_id 要消耗序列号所述的 card_id,创建卡券时use_custom_code 填写 true 时必填。
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "card":{"card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc"},
+     *  "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA"
+     * }
+     */
+    public function consumeCardCode($code, $card_id = '')
+    {
+        $data = ['code' => $code];
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_CONSUME . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * code 解码
+     * @param string $encrypt_code 通过 choose_card_info 获取的加密字符串
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "code":"751234212312"
+     *  }
+     */
+    public function decryptCardCode($encrypt_code)
+    {
+        $data = [
+            'encrypt_code' => $encrypt_code,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_DECRYPT . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 查询 code 的有效性(非自定义 code)
+     * @param string $code
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "openid":"oFS7Fjl0WsZ9AMZqrI80nbIq8xrA",    //用户 openid
+     *  "card":{
+     *      "card_id":"pFS7Fjg8kV1IdDz01r4SQwMkuCKc",
+     *      "begin_time": 1404205036,               //起始使用时间
+     *      "end_time": 1404205036,                 //结束时间
+     *  }
+     * }
+     */
+    public function checkCardCode($code)
+    {
+        $data = [
+            'code' => $code,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_GET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 批量查询卡列表
+     * @param $offset  开始拉取的偏移,默认为0从头开始
+     * @param $count   需要查询的卡片的数量(数量最大50,默认50)
+     * @return boolean|array
+     * {
+     *  "errcode":0,
+     *  "errmsg":"ok",
+     *  "card_id_list":["ph_gmt7cUVrlRk8swPwx7aDyF-pg"],    //卡 id 列表
+     *  "total_num":1                                       //该商户名下 card_id 总数
+     * }
+     */
+    public function getCardIdList($offset = 0, $count = 50)
+    {
+        if ($count > 50)
+            $count = 50;
+        $data = [
+            'offset' => $offset,
+            'count'  => $count,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_BATCHGET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 更改 code
+     * 为确保转赠后的安全性,微信允许自定义code的商户对已下发的code进行更改。
+     * 注:为避免用户疑惑,建议仅在发生转赠行为后(发生转赠后,微信会通过事件推送的方式告知商户被转赠的卡券code)对用户的code进行更改。
+     * @param string $code 卡券的 code 编码
+     * @param string $card_id 卡券 ID
+     * @param string $new_code 新的卡券 code 编码
+     * @return boolean
+     */
+    public function updateCardCode($code, $card_id, $new_code)
+    {
+        $data = [
+            'code'     => $code,
+            'card_id'  => $card_id,
+            'new_code' => $new_code,
+        ];
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 设置卡券失效
+     * 设置卡券失效的操作不可逆
+     * @param string $code 需要设置为失效的 code
+     * @param string $card_id 自定义 code 的卡券必填。非自定义 code 的卡券不填。
+     * @return boolean
+     */
+    public function unavailableCardCode($code, $card_id = '')
+    {
+        $data = [
+            'code' => $code,
+        ];
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_CODE_UNAVAILABLE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 库存修改
+     * @param string $data
+     * @return boolean
+     */
+    public function modifyCardStock($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MODIFY_STOCK . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 更新门票
+     * @param string $data
+     * @return boolean
+     */
+    public function updateMeetingCard($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEETINGCARD_UPDATEUSER . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 激活/绑定会员卡
+     * @param string $data 具体结构请参看卡券开发文档(6.1.1 激活/绑定会员卡)章节
+     * @return boolean
+     */
+    public function activateMemberCard($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_ACTIVATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 会员卡交易
+     * 会员卡交易后每次积分及余额变更需通过接口通知微信,便于后续消息通知及其他扩展功能。
+     * @param string $data 具体结构请参看卡券开发文档(6.1.2 会员卡交易)章节
+     * @return boolean|array
+     */
+    public function updateMemberCard($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_MEMBERCARD_UPDATEUSER . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 更新红包金额
+     * @param string $code 红包的序列号
+     * @param $balance          红包余额
+     * @param string $card_id 自定义 code 的卡券必填。非自定义 code 可不填。
+     * @return boolean|array
+     */
+    public function updateLuckyMoney($code, $balance, $card_id = '')
+    {
+        $data = [
+            'code'    => $code,
+            'balance' => $balance,
+        ];
+        if ($card_id)
+            $data['card_id'] = $card_id;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_LUCKYMONEY_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 设置卡券测试白名单
+     * @param string $openid 测试的 openid 列表
+     * @param string $user 测试的微信号列表
+     * @return boolean
+     */
+    public function setCardTestWhiteList($openid = [], $user = [])
+    {
+        $data = [];
+        if (count($openid) > 0)
+            $data['openid'] = $openid;
+        if (count($user) > 0)
+            $data['username'] = $user;
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_TESTWHILELIST_SET . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 申请设备ID
+     * [applyShakeAroundDevice 申请配置设备所需的UUID、Major、Minor。
+     * 若激活率小于50%,不能新增设备。单次新增设备超过500 个,需走人工审核流程。
+     * 审核通过后,可用迒回的批次ID 用“查询设备列表”接口拉取本次申请的设备ID]
+     * @param array $data
+     * array(
+     *      "quantity" => 3,         //申请的设备ID 的数量,单次新增设备超过500 个,需走人工审核流程(必填)
+     *      "apply_reason" => "测试",//申请理由(必填)
+     *      "comment" => "测试专用", //备注(非必填)
+     *      "poi_id" => 1234         //设备关联的门店ID(非必填)
+     * )
+     * @return boolean|mixed
+     * {
+     * "data": {
+     * "apply_id": 123,
+     * "device_identifiers":[
+     * {
+     * "device_id":10100,
+     * "uuid":"FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+     * "major":10001,
+     * "minor":10002
+     * }
+     * ]
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     *
+     * apply_id:申请的批次ID,可用在“查询设备列表”接口按批次查询本次申请成功的设备ID
+     * device_identifiers:指定的设备ID 列表
+     * device_id:设备编号
+     * uuid、major、minor
+     * audit_status:审核状态。0:审核未通过、1:审核中、2:审核已通过;审核会在三个工作日内完成
+     * audit_comment:审核备注,包括审核不通过的原因
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午1:24:06
+     * @copyright Show More
+     */
+    public function applyShakeAroundDevice($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_APPLYID . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 编辑设备信息
+     * [updateShakeAroundDevice 编辑设备的备注信息。可用设备ID或完整的UUID、Major、Minor指定设备,二者选其一。]
+     * @param array $data
+     * array(
+     *      "device_identifier" => array(
+     *                "device_id" => 10011,   //当提供了device_id则不需要使用uuid、major、minor,反之亦然
+     *                "uuid" => "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+     *                "major" => 1002,
+     *                "minor" => 1223
+     *      ),
+     *      "comment" => "测试专用", //备注(非必填)
+     * )
+     * {
+     * "data": {
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @return boolean
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-20 23:45:00
+     */
+    public function updateShakeAroundDevice($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 查询设备列表
+     * [searchShakeAroundDevice 查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。
+     * 可指定设备ID 或完整的UUID、Major、Minor 查询,也可批量拉取设备信息列表。]
+     * @param array $data
+     * $data 三种格式:
+     * ①查询指定设备时:$data = array(
+     *                              "device_identifiers" => array(
+     *                                                          array(
+     *                                                              "device_id" => 10100,
+     *                                                              "uuid" => "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
+     *                                                              "major" => 10001,
+     *                                                              "minor" => 10002
+     *                                                          )
+     *                                                      )
+     *                              );
+     * device_identifiers:指定的设备
+     * device_id:设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * uuid、major、minor:三个信息需填写完整,若填了设备编号,则可不填此信息
+     * +-------------------------------------------------------------------------------------------------------------
+     * ②需要分页查询或者指定范围内的设备时: $data = array(
+     *                                                  "begin" => 0,
+     *                                                  "count" => 3
+     *                                               );
+     * begin:设备列表的起始索引值
+     * count:待查询的设备个数
+     * +-------------------------------------------------------------------------------------------------------------
+     * ③当需要根据批次ID 查询时: $data = array(
+     *                                      "apply_id" => 1231,
+     *                                      "begin" => 0,
+     *                                      "count" => 3
+     *                                    );
+     * apply_id:批次ID
+     * +-------------------------------------------------------------------------------------------------------------
+     * @return boolean|mixed
+     *正确迒回JSON 数据示例:
+     *字段说明
+     * {
+     * "data": {
+     * "devices": [          //指定的设备信息列表
+     * {
+     * "comment": "", //设备的备注信息
+     * "device_id": 10097, //设备编号
+     * "major": 10001,
+     * "minor": 12102,
+     * "page_ids": "15369", //与此设备关联的页面ID 列表,用逗号隔开
+     * "status": 1, //激活状态,0:未激活,1:已激活(但不活跃),2:活跃
+     * "poi_id": 0, //门店ID
+     * "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+     * },
+     * {
+     * "comment": "", //设备的备注信息
+     * "device_id": 10098, //设备编号
+     * "major": 10001,
+     * "minor": 12103,
+     * "page_ids": "15368", //与此设备关联的页面ID 列表,用逗号隔开
+     * "status": 1, //激活状态,0:未激活,1:已激活(但不活跃),2:活跃
+     * "poi_id": 0, //门店ID
+     * "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+     * }
+     * ],
+     * "total_count": 151 //商户名下的设备总量
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午1:45:42
+     * @copyright Show More
+     */
+    public function searchShakeAroundDevice($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_SEARCH . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [bindLocationShakeAroundDevice 配置设备与门店的关联关系]
+     * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * @param int $poi_id 待关联的门店ID
+     * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:14:00
+     * @copyright Show More
+     */
+    public function bindLocationShakeAroundDevice($device_id, $poi_id, $uuid = '', $major = 0, $minor = 0)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$device_id) {
+            if (!$uuid || !$major || !$minor) {
+                return false;
+            }
+            $device_identifier = [
+                'uuid'  => $uuid,
+                'major' => $major,
+                'minor' => $minor,
+            ];
+        } else {
+            $device_identifier = [
+                'device_id' => $device_id,
+            ];
+        }
+        $data   = [
+            'device_identifier' => $device_identifier,
+            'poi_id'            => $poi_id,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDLOCATION . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json; //这个可以更改为返回true
+        }
+        return false;
+    }
+
+    /**
+     * [bindPageShakeAroundDevice 配置设备与页面的关联关系。
+     * 支持建立或解除关联关系,也支持新增页面或覆盖页面等操作。
+     * 配置完成后,在此设备的信号范围内,即可摇出关联的页面信息。
+     * 若设备配置多个页面,则随机出现页面信息]
+     * @param string $device_id 设备编号,若填了UUID、major、minor,则可不填设备编号,若二者都填,则以设备编号为优先
+     * @param array $page_ids 待关联的页面列表
+     * @param number $bind 关联操作标志位, 0 为解除关联关系,1 为建立关联关系
+     * @param number $append 新增操作标志位, 0 为覆盖,1 为新增
+     * @param string $uuid UUID、major、minor,三个信息需填写完整,若填了设备编号,则可不填此信息
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:31:00
+     * @copyright Show More
+     */
+    public function bindPageShakeAroundDevice($device_id, $page_ids = [], $bind = 1, $append = 1, $uuid = '', $major = 0, $minor = 0)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$device_id) {
+            if (!$uuid || !$major || !$minor) {
+                return false;
+            }
+            $device_identifier = [
+                'uuid'  => $uuid,
+                'major' => $major,
+                'minor' => $minor,
+            ];
+        } else {
+            $device_identifier = [
+                'device_id' => $device_id,
+            ];
+        }
+        $data   = [
+            'device_identifier' => $device_identifier,
+            'page_ids'          => $page_ids,
+            'bind'              => $bind,
+            'append'            => $append,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_DEVICE_BINDPAGE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 上传在摇一摇页面展示的图片素材
+     * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
+     * @param array $data {"media":'@Path\filename.jpg'} 格式限定为:jpg,jpeg,png,gif,图片大小建议120px*120 px,限制不超过200 px *200 px,图片需为正方形。
+     * @return boolean|array
+     * {
+     * "data": {
+     * "pic_url":"http://shp.qpic.cn/wechat_shakearound_pic/0/1428377032e9dd2797018cad79186e03e8c5aec8dc/120"
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * }
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-21 00:51:00
+     */
+    public function uploadShakeAroundMedia($data)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_MATERIAL_ADD . 'access_token=' . $this->access_token, $data, true);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [addShakeAroundPage 增加摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。]
+     * @param string $title 在摇一摇页面展示的主标题,不超过6 个字
+     * @param string $description 在摇一摇页面展示的副标题,不超过7 个字
+     * @param sting $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200
+     * @param string $page_url 跳转链接
+     * @param string $comment 页面的备注信息,不超过15 个字,可不填
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * "page_id": 28840 //新增页面的页面id
+     * }
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午2:57:09
+     * @copyright Show More
+     */
+    public function addShakeAroundPage($title, $description, $icon_url, $page_url, $comment = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            "title"       => $title,
+            "description" => $description,
+            "icon_url"    => $icon_url,
+            "page_url"    => $page_url,
+            "comment"     => $comment,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_ADD . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [updateShakeAroundPage 编辑摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。]
+     * @param int $page_id
+     * @param string $title 在摇一摇页面展示的主标题,不超过6 个字
+     * @param string $description 在摇一摇页面展示的副标题,不超过7 个字
+     * @param sting $icon_url 在摇一摇页面展示的图片, 格式限定为:jpg,jpeg,png,gif; 建议120*120 , 限制不超过200*200
+     * @param string $page_url 跳转链接
+     * @param string $comment 页面的备注信息,不超过15 个字,可不填
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * "page_id": 28840 //编辑页面的页面ID
+     * }
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:02:51
+     * @copyright Show More
+     */
+    public function updateShakeAroundPage($page_id, $title, $description, $icon_url, $page_url, $comment = '')
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            "page_id"     => $page_id,
+            "title"       => $title,
+            "description" => $description,
+            "icon_url"    => $icon_url,
+            "page_url"    => $page_url,
+            "comment"     => $comment,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_UPDATE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [searchShakeAroundPage 查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
+     * 提供两种查询方式,①可指定页面ID 查询,②也可批量拉取页面列表。]
+     * @param array $page_ids
+     * @param int $begin
+     * @param int $count
+     * ①需要查询指定页面时:
+     * {
+     * "page_ids":[12345, 23456, 34567]
+     * }
+     * +-------------------------------------------------------------------------------------------------------------
+     * ②需要分页查询或者指定范围内的页面时:
+     * {
+     * "begin": 0,
+     * "count": 3
+     * }
+     * +-------------------------------------------------------------------------------------------------------------
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * "pages": [
+     * {
+     * "comment": "just for test",
+     * "description": "test",
+     * "icon_url": "https://www.baidu.com/img/bd_logo1.png",
+     * "page_id": 28840,
+     * "page_url": "http://xw.qq.com/testapi1",
+     * "title": "测试1"
+     * },
+     * {
+     * "comment": "just for test",
+     * "description": "test",
+     * "icon_url": "https://www.baidu.com/img/bd_logo1.png",
+     * "page_id": 28842,
+     * "page_url": "http://xw.qq.com/testapi2",
+     * "title": "测试2"
+     * }
+     * ],
+     * "total_count": 2
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     *字段说明:
+     *total_count 商户名下的页面总数
+     *page_id 摇周边页面唯一ID
+     *title 在摇一摇页面展示的主标题
+     *description 在摇一摇页面展示的副标题
+     *icon_url 在摇一摇页面展示的图片
+     *page_url 跳转链接
+     *comment 页面的备注信息
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:12:17
+     * @copyright Show More
+     */
+    public function searchShakeAroundPage($page_ids = [], $begin = 0, $count = 1)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!empty($page_ids)) {
+            $data = [
+                'page_ids' => $page_ids,
+            ];
+        } else {
+            $data = [
+                'begin' => $begin,
+                'count' => $count,
+            ];
+        }
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_SEARCH . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [deleteShakeAroundPage 删除已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。
+     * 只有页面与设备没有关联关系时,才可被删除。]
+     * @param array $page_ids
+     * {
+     * "page_ids":[12345,23456,34567]
+     * }
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:23:00
+     * @copyright Show More
+     */
+    public function deleteShakeAroundPage($page_ids = [])
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'page_ids' => $page_ids,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_PAGE_DELETE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [getShakeInfoShakeAroundUser 获取设备信息,包括UUID、major、minor,以及距离、openID 等信息。]
+     * @param string $ticket 摇周边业务的ticket,可在摇到的URL 中得到,ticket生效时间为30 分钟
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": {
+     * "page_id ": 14211,
+     * "beacon_info": {
+     * "distance": 55.00620700469034,
+     * "major": 10001,
+     * "minor": 19007,
+     * "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
+     * },
+     * "openid": "oVDmXjp7y8aG2AlBuRpMZTb1-cmA"
+     * },
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * 字段说明:
+     * beacon_info 设备信息,包括UUID、major、minor,以及距离
+     * UUID、major、minor UUID、major、minor
+     * distance Beacon 信号与手机的距离
+     * page_id 摇周边页面唯一ID
+     * openid 商户AppID 下用户的唯一标识
+     * poi_id 门店ID,有的话则返回,没有的话不会在JSON 格式内
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-3-25 下午3:28:20
+     * @copyright Show More
+     */
+    public function getShakeInfoShakeAroundUser($ticket)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = ['ticket' => $ticket];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_USER_GETSHAKEINFO . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * [deviceShakeAroundStatistics 以设备为维度的数据统计接口。
+     * 查询单个设备进行摇周边操作的人数、次数,点击摇周边消息的人数、次数;查询的最长时间跨度为30天。]
+     * @param int $device_id 设备编号,若填了UUID、major、minor,即可不填设备编号,二者选其一
+     * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天
+     * @param int $end_date 结束日期时间戳,最长时间跨度为30 天
+     * @param string $uuid UUID、major、minor,三个信息需填写完成,若填了设备编辑,即可不填此信息,二者选其一
+     * @param int $major
+     * @param int $minor
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": [
+     * {
+     * "click_pv": 0,
+     * "click_uv": 0,
+     * "ftime": 1425052800,
+     * "shake_pv": 0,
+     * "shake_uv": 0
+     * },
+     * {
+     * "click_pv": 0,
+     * "click_uv": 0,
+     * "ftime": 1425139200,
+     * "shake_pv": 0,
+     * "shake_uv": 0
+     * }
+     * ],
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * 字段说明:
+     * ftime 当天0 点对应的时间戳
+     * click_pv 点击摇周边消息的次数
+     * click_uv 点击摇周边消息的人数
+     * shake_pv 摇周边的次数
+     * shake_uv 摇周边的人数
+     * @access public
+     * @author polo<gao.bo168@gmail.com>
+     * @version 2015-4-21 00:39:00
+     * @copyright Show More
+     */
+    public function deviceShakeAroundStatistics($device_id, $begin_date, $end_date, $uuid = '', $major = 0, $minor = 0)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$device_id) {
+            if (!$uuid || !$major || !$minor) {
+                return false;
+            }
+            $device_identifier = [
+                'uuid'  => $uuid,
+                'major' => $major,
+                'minor' => $minor,
+            ];
+        } else {
+            $device_identifier = [
+                'device_id' => $device_id,
+            ];
+        }
+        $data   = [
+            'device_identifier' => $device_identifier,
+            'begin_date'        => $begin_date,
+            'end_date'          => $end_date,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+
+    /**
+     * [pageShakeAroundStatistics 以页面为维度的数据统计接口。
+     * 查询单个页面通过摇周边摇出来的人数、次数,点击摇周边页面的人数、次数;查询的最长时间跨度为30天。]
+     * @param int $page_id 指定页面的ID
+     * @param int $begin_date 起始日期时间戳,最长时间跨度为30 天
+     * @param int $end_date 结束日期时间戳,最长时间跨度为30 天
+     * @return boolean|mixed
+     * 正确返回JSON 数据示例:
+     * {
+     * "data": [
+     * {
+     * "click_pv": 0,
+     * "click_uv": 0,
+     * "ftime": 1425052800,
+     * "shake_pv": 0,
+     * "shake_uv": 0
+     * },
+     * {
+     * "click_pv": 0,
+     * "click_uv": 0,
+     * "ftime": 1425139200,
+     * "shake_pv": 0,
+     * "shake_uv": 0
+     * }
+     * ],
+     * "errcode": 0,
+     * "errmsg": "success."
+     * }
+     * 字段说明:
+     * ftime 当天0 点对应的时间戳
+     * click_pv 点击摇周边消息的次数
+     * click_uv 点击摇周边消息的人数
+     * shake_pv 摇周边的次数
+     * shake_uv 摇周边的人数
+     * @author binsee<binsee@163.com>
+     * @version 2015-4-21 00:43:00
+     */
+    public function pageShakeAroundStatistics($page_id, $begin_date, $end_date)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $data   = [
+            'page_id'    => $page_id,
+            'begin_date' => $begin_date,
+            'end_date'   => $end_date,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::SHAKEAROUND_STATISTICS_DEVICE . 'access_token=' . $this->access_token, self::json_encode($data));
+        $this->log($result);
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
+     * 根据订单ID获取订单详情
+     * @param string $order_id 订单ID
+     * @return order array|bool
+     */
+    public function getOrderByID($order_id)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$order_id) return false;
+
+        $data   = [
+            'order_id' => $order_id,
+        ];
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::MERCHANT_ORDER_GETBYID . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode']) && $json['errcode']) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json['order'];
+        }
+        return false;
+    }
+
+    /**
+     * 根据订单状态/创建时间获取订单详情
+     * @param int $status 订单状态(不带该字段-全部状态, 2-待发货, 3-已发货, 5-已完成, 8-维权中, )
+     * @param int $begintime 订单创建时间起始时间(不带该字段则不按照时间做筛选)
+     * @param int $endtime 订单创建时间终止时间(不带该字段则不按照时间做筛选)
+     * @return order list array|bool
+     */
+    public function getOrderByFilter($status = null, $begintime = null, $endtime = null)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+
+        $data = [];
+
+        $valid_status = [2, 3, 5, 8];
+        if (is_numeric($status) && in_array($status, $valid_status)) {
+            $data['status'] = $status;
+        }
+
+        if (is_numeric($begintime) && is_numeric($endtime)) {
+            $data['begintime'] = $begintime;
+            $data['endtime']   = $endtime;
+        }
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::MERCHANT_ORDER_GETBYFILTER . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode']) && $json['errcode']) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json['order_list'];
+        }
+        return false;
+    }
+
+    /**
+     * 设置订单发货信息
+     * @param string $order_id 订单 ID
+     * @param int $need_delivery 商品是否需要物流(0-不需要,1-需要)
+     * @param string $delivery_company 物流公司 ID
+     * @param string $delivery_track_no 运单 ID
+     * @param int $is_others 是否为 6.4.5 表之外的其它物流公司(0-否,1-是)
+     * @return bool
+     */
+    public function setOrderDelivery($order_id, $need_delivery = 0, $delivery_company = null, $delivery_track_no = null, $is_others = 0)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$order_id) return false;
+
+        $data             = [];
+        $data['order_id'] = $order_id;
+        if ($need_delivery) {
+            $data['delivery_company']  = $delivery_company;
+            $data['delivery_track_no'] = $delivery_track_no;
+            $data['is_others']         = $is_others;
+        } else {
+            $data['need_delivery'] = $need_delivery;
+        }
+
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::MERCHANT_ORDER_SETDELIVERY . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode']) && $json['errcode']) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 关闭订单
+     * @param string $order_id 订单 ID
+     * @return bool
+     */
+    public function closeOrder($order_id)
+    {
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        if (!$order_id) return false;
+
+        $data = [
+            'order_id' => $order_id,
+        ];
+
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::MERCHANT_ORDER_CLOSE . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (isset($json['errcode']) && $json['errcode']) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private function parseSkuInfo($skuInfo)
+    {
+        $skuInfo = str_replace("\$", "", $skuInfo);
+        $matches = explode(";", $skuInfo);
+
+        $result = [];
+        foreach ($matches as $matche) {
+            $arrs             = explode(":", $matche);
+            $result[$arrs[0]] = $arrs[1];
+        }
+
+        return $result;
+    }
+
+    /**
+     * 获取订单SkuInfo - 订单付款通知
+     * 当Event为 merchant_order(订单付款通知)
+     * @return array|boolean
+     */
+    public function getRevOrderSkuInfo()
+    {
+        if (isset($this->_receive['SkuInfo']))     //订单 SkuInfo
+            return $this->parseSkuInfo($this->_receive['SkuInfo']);
+        else
+            return false;
+    }
+}
+
+/**
+ * PKCS7Encoder class
+ *
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder
+{
+    public static $block_size = 32;
+
+    /**
+     * 对需要加密的明文进行填充补位
+     * @param $text 需要进行填充补位操作的明文
+     * @return 补齐明文字符串
+     */
+    function encode($text)
+    {
+        $block_size  = PKCS7Encoder::$block_size;
+        $text_length = strlen($text);
+        //计算需要填充的位数
+        $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
+        if ($amount_to_pad == 0) {
+            $amount_to_pad = PKCS7Encoder::block_size;
+        }
+        //获得补位所用的字符
+        $pad_chr = chr($amount_to_pad);
+        $tmp     = "";
+        for ($index = 0; $index < $amount_to_pad; $index++) {
+            $tmp .= $pad_chr;
+        }
+        return $text . $tmp;
+    }
+
+    /**
+     * 对解密后的明文进行补位删除
+     * @param decrypted 解密后的明文
+     * @return 删除填充补位后的明文
+     */
+    function decode($text)
+    {
+
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
+            $pad = 0;
+        }
+        return substr($text, 0, (strlen($text) - $pad));
+    }
+
+}
+
+/**
+ * Prpcrypt class
+ *
+ * 提供接收和推送给公众平台消息的加解密接口.
+ */
+class Prpcrypt
+{
+    public $key;
+
+    function __construct($k)
+    {
+        $this->key = base64_decode($k . "=");
+    }
+
+    /**
+     * 兼容老版本php构造函数,不能在 __construct() 方法前边,否则报错
+     */
+    function Prpcrypt($k)
+    {
+        $this->key = base64_decode($k . "=");
+    }
+
+    /**
+     * 对明文进行加密
+     * @param string $text 需要加密的明文
+     * @return string 加密后的密文
+     */
+    public function encrypt($text, $appid)
+    {
+
+        try {
+            //获得16位随机字符串,填充到明文之前
+            $random = $this->getRandomStr();//"aaaabbbbccccdddd";
+            $text   = $random . pack("N", strlen($text)) . $text . $appid;
+            // 网络字节序
+            $size   = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv     = substr($this->key, 0, 16);
+            //使用自定义的填充方式对明文进行补位填充
+            $pkc_encoder = new PKCS7Encoder;
+            $text        = $pkc_encoder->encode($text);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //加密
+            $encrypted = mcrypt_generic($module, $text);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+
+            //			print(base64_encode($encrypted));
+            //使用BASE64对加密后的字符串进行编码
+            return [ErrorCode::$OK, base64_encode($encrypted)];
+        } catch (Exception $e) {
+            //print $e;
+            return [ErrorCode::$EncryptAESError, null];
+        }
+    }
+
+    /**
+     * 对密文进行解密
+     * @param string $encrypted 需要解密的密文
+     * @return string 解密得到的明文
+     */
+    public function decrypt($encrypted, $appid)
+    {
+
+        try {
+            //使用BASE64对需要解密的字符串进行解码
+            $ciphertext_dec = base64_decode($encrypted);
+            $module         = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+            $iv             = substr($this->key, 0, 16);
+            mcrypt_generic_init($module, $this->key, $iv);
+            //解密
+            $decrypted = mdecrypt_generic($module, $ciphertext_dec);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+        } catch (Exception $e) {
+            return [ErrorCode::$DecryptAESError, null];
+        }
+
+
+        try {
+            //去除补位字符
+            $pkc_encoder = new PKCS7Encoder;
+            $result      = $pkc_encoder->decode($decrypted);
+            //去除16位随机字符串,网络字节序和AppId
+            if (strlen($result) < 16)
+                return "";
+            $content     = substr($result, 16, strlen($result));
+            $len_list    = unpack("N", substr($content, 0, 4));
+            $xml_len     = $len_list[1];
+            $xml_content = substr($content, 4, $xml_len);
+            $from_appid  = substr($content, $xml_len + 4);
+            if (!$appid)
+                $appid = $from_appid;
+            //如果传入的appid是空的,则认为是订阅号,使用数据中提取出来的appid
+        } catch (Exception $e) {
+            //print $e;
+            return [ErrorCode::$IllegalBuffer, null];
+        }
+        if ($from_appid != $appid)
+            return [ErrorCode::$ValidateAppidError, null];
+        //不注释上边两行,避免传入appid是错误的情况
+        return [0, $xml_content, $from_appid]; //增加appid,为了解决后面加密回复消息的时候没有appid的订阅号会无法回复
+
+    }
+
+
+    /**
+     * 随机生成16位字符串
+     * @return string 生成的字符串
+     */
+    function getRandomStr()
+    {
+
+        $str     = "";
+        $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+        $max     = strlen($str_pol) - 1;
+        for ($i = 0; $i < 16; $i++) {
+            $str .= $str_pol[mt_rand(0, $max)];
+        }
+        return $str;
+    }
+
+}
+
+/**
+ * error code
+ * 仅用作类内部使用,不用于官方API接口的errCode码
+ */
+class ErrorCode
+{
+    public static $OK = 0;
+    public static $ValidateSignatureError = 40001;
+    public static $ParseXmlError = 40002;
+    public static $ComputeSignatureError = 40003;
+    public static $IllegalAesKey = 40004;
+    public static $ValidateAppidError = 40005;
+    public static $EncryptAESError = 40006;
+    public static $DecryptAESError = 40007;
+    public static $IllegalBuffer = 40008;
+    public static $EncodeBase64Error = 40009;
+    public static $DecodeBase64Error = 40010;
+    public static $GenReturnXmlError = 40011;
+    public static $errCode
+        = [
+            '0'     => '处理成功',
+            '40001' => '校验签名失败',
+            '40002' => '解析xml失败',
+            '40003' => '计算签名失败',
+            '40004' => '不合法的AESKey',
+            '40005' => '校验AppID失败',
+            '40006' => 'AES加密失败',
+            '40007' => 'AES解密失败',
+            '40008' => '公众平台发送的xml不合法',
+            '40009' => 'Base64编码失败',
+            '40010' => 'Base64解码失败',
+            '40011' => '公众帐号生成回包xml失败',
+        ];
+
+    public static function getErrText($err)
+    {
+        if (isset(self::$errCode[$err])) {
+            return self::$errCode[$err];
+        } else {
+            return false;
+        };
+    }
+}

+ 287 - 0
app/love/controller/ActiveController.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: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\love\controller;
+
+use app\common\Fun;
+use app\love\model\ActiveApplyModel;
+use app\love\model\ActiveModel;
+use app\love\model\UserFriendModel;
+use app\love\model\UserInviteModel;
+
+class ActiveController extends LoveBaseController
+{
+    private $_age = [[0, 100], [18, 25], [25, 30], [30, 40], [40, 50], [50, 100]];
+    private $_high = [[0, 250], [150, 160], [161, 170], [171, 180], [180, 250]];
+    private $_weight = [[0, 150], [40, 50], [51, 60], [61, 70], [71, 80], [81, 150]];
+
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $list = ActiveModel::where('status', 1)->order('create_time desc')->select();
+        foreach ($list as $v) {
+            $v['main_image'] = cmf_get_image_preview_url($v['main_image']);
+            $v['start_time'] = date('m-d', $v['start_time']);
+            $v['end_time']   = date('m-d', $v['end_time']);
+        }
+        $this->assign('list', $list);
+
+        //未读消息数
+        $unread_num = UserFriendModel::where('user_id', cmf_get_current_user_id())->sum('unread_num');
+        $invite_num = UserInviteModel::where('to_id', cmf_get_current_user_id())
+            ->where('status', 1)
+            ->count();
+        $this->assign('unread_num', $unread_num + $invite_num);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('活动不存在或已结束');
+        }
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            $this->error('活动不存在或已结束');
+        }
+        $this->assign('id', $id);
+
+        $active                = ActiveModel::get($id);
+        $active['time_status'] = 0;
+        if ($active['start_time'] > time()) {
+            $active['time_status'] = 1;
+        }
+        if ($active['end_time'] < time()) {
+            $active['time_status'] = 2;
+        }
+        $active['start_time'] = date('m-d', $active['start_time']);
+        $active['end_time']   = date('m-d', $active['end_time']);
+        $active['main_image'] = cmf_get_image_preview_url($active['main_image']);
+        $this->assign('info', $active);
+
+        //状态
+        $apply = ActiveApplyModel::where([
+            ['user_id', '=', $this->user['id']],
+            ['active_id', '=', $id],
+        ])->find();
+        $this->assign('apply', $apply ?: 'false');
+
+        if ($apply && $apply['check_status'] == 2) {
+            $this->assign('share_image_url', $active['main_image']);
+            $this->assign('share_title', $active['title']);
+            $this->assign('share_link', url('love/login/index', '', true, true) . '?active_id=' . $id);
+            $this->assign('share_desc', $active['describe']);
+        }
+
+        return $this->fetch();
+    }
+
+    /**
+     * 参加活动
+     */
+    public function apply()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('活动不存在或已结束');
+        }
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            $this->error('活动不存在或已结束');
+        }
+
+        $check = ActiveApplyModel::get(['active_id'=>$id,'user_id'=>$this->user['id']]);
+        if (!empty($check)) {
+            $this->error('您已报名!');
+        }
+
+        //参加
+        ActiveApplyModel::create([
+            'active_id'   => $id,
+            'user_id'     => $this->user['id'],
+            'create_time' => time(),
+        ]);
+        $this->success('报名成功');
+    }
+
+    /**
+     * 取消参加活动
+     */
+    public function cancel()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('活动不存在或已结束');
+        }
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            $this->error('活动不存在或已结束');
+        }
+
+        $check = ActiveApplyModel::get(['active_id'=>$id,'user_id'=>$this->user['id']]);
+        if (empty($check)) {
+            $this->error('您未报名,无需取消!');
+        }
+
+        $check->delete();
+        $this->success('取消成功');
+    }
+
+    /**
+     * 嘉宾
+     */
+    public function people()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('活动不存在或已结束');
+        }
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            $this->error('活动不存在或已结束');
+        }
+        $this->assign('id', $id);
+
+        $where = [
+            ['a.active_id', '=', $id],
+            ['a.check_status', '=', 2],
+            ['u.check_status', '=', 2],
+            ['u.is_complete', '=', 1],
+            ['u.id', '<>', cmf_get_current_user_id()],
+        ];
+        $list  = ActiveApplyModel::alias('a')
+            ->field('u.*,a.site_status')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where($where)
+            ->order('a.site_status asc')
+            ->limit(6)
+            ->select();
+
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->assign('list', $list);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 列表
+     */
+    public function peopleList()
+    {
+        $param = $this->request->post();
+
+        $where = [
+            ['a.active_id', '=', $param['active_id']],
+            ['a.check_status', '=', 2],
+            ['u.check_status', '=', 2],
+            ['u.is_complete', '=', 1],
+            ['u.id', '<>', cmf_get_current_user_id()],
+        ];
+        if (!empty($param['age'])) {
+            $age        = $this->_age[$param['age']];
+            $start_time = strtotime("-{$age[1]} year");
+            $end_time   = strtotime("-{$age[0]} year");
+            $where[]    = ['u.birthday', 'between', [$start_time, $end_time]];
+        }
+        if (!empty($param['site_status'])) {
+            $where[] = ['a.site_status', '=', $param['site_status']];
+        }
+        if (!empty($param['sex'])) {
+            $where[] = ['u.sex', '=', $param['sex']];
+        }
+        if (!empty($param['high'])) {
+            $high    = $this->_high[$param['high']];
+            $where[] = ['u.high', 'between', [$high[0], $high[1]]];
+        }
+        if (!empty($param['weight'])) {
+            $weight  = $this->_weight[$param['weight']];
+            $where[] = ['u.weight', 'between', [$weight[0], $weight[1]]];
+        }
+        if (!empty($param['education'])) {
+            $where[] = ['u.education', '=', $param['education']];
+        }
+        if (!empty($param['id'])) {
+            $where[] = ['a.user_no', '=', $param['id']];
+        }
+
+        $list = ActiveApplyModel::alias('a')
+            ->field('u.*,a.site_status')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where($where)
+            ->order('a.site_status asc')
+            ->limit(6)
+            ->page($param['page'])
+            ->select();
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->result($list, 1);
+    }
+
+    /**
+     * 排行榜
+     */
+    public function rank()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('活动不存在或已结束');
+        }
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            $this->error('活动不存在或已结束');
+        }
+        $this->assign('id', $id);
+
+        //列表
+        $where = [
+            ['a.active_id', '=', $id],
+            ['a.check_status', '=', 2],
+            ['u.check_status', '=', 2],
+            ['u.is_complete', '=', 1],
+        ];
+        $list1 = ActiveApplyModel::alias('a')
+            ->field('u.*,a.site_status')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where($where)
+            ->where('sex', 2)
+            ->order('u.invite_num desc')
+            ->limit(10)
+            ->select();
+        foreach ($list1 as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->assign('list1', $list1);
+
+        $list2 = ActiveApplyModel::alias('a')
+            ->field('u.*,a.site_status')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where($where)
+            ->where('sex', 1)
+            ->order('u.invite_num desc')
+            ->limit(10)
+            ->select();
+        foreach ($list2 as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->assign('list2', $list2);
+
+        return $this->fetch();
+    }
+}

+ 213 - 0
app/love/controller/AdminActiveController.php

@@ -0,0 +1,213 @@
+<?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: Powerless < wzxaini9@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\love\controller;
+
+use app\common\Excel;
+use app\common\Fun;
+use app\love\model\ActiveApplyModel;
+use app\love\model\ActiveModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActiveController extends AdminBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $list = ActiveModel::paginate(10);
+        foreach ($list as $v) {
+            $v['url'] = urlencode(url('love/Scene/wechat', ['active_id' => $v['id']], true, true));
+        }
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+
+        // 渲染模板输出
+        return $this->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        return $this->fetch();
+    }
+
+    /**
+     * 添加提交
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+            $post = $data['post'];
+
+            if (empty($post['start_time'])) {
+                $this->error('请选择开始时间');
+            }
+
+            if (empty($post['end_time'])) {
+                $this->error('请选择结束时间');
+            }
+
+            $post['create_time'] = $post['update_time'] = time();
+            $post['start_time']  = strtotime($post['start_time']);
+            $post['end_time']    = strtotime($post['end_time']);
+            ActiveModel::create($post);
+
+            $this->success('添加成功!', url('index'));
+        }
+
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit()
+    {
+        $id   = $this->request->param('id', 0, 'intval');
+        $post = ActiveModel::get($id);
+
+        $this->assign('post', $post);
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑提交
+     */
+    public function editPost()
+    {
+
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+            $post = $data['post'];
+
+            if (empty($post['start_time'])) {
+                $this->error('请选择开始时间');
+            }
+
+            if (empty($post['end_time'])) {
+                $this->error('请选择结束时间');
+            }
+
+            $post['create_time'] = $post['update_time'] = time();
+            $post['start_time']  = strtotime($post['start_time']);
+            $post['end_time']    = strtotime($post['end_time']);
+            ActiveModel::update($post, ['id' => $post['id']]);
+
+            $this->success('编辑成功!', url('index'));
+        }
+    }
+
+    /**
+     * 审核列表
+     */
+    public function applyList()
+    {
+        $param = $this->request->param();
+        $this->assign('id', $param['id']);
+        $this->assign('check_status', $param['check_status'] ?? 0);
+        $this->assign('sex', $param['sex'] ?? 0);
+
+        $where = [
+            ['active_id', '=', $param['id']],
+        ];
+        if (!empty($param['check_status'])) {
+            $where[] = ['cmf_active_apply.check_status', '=', $param['check_status']];
+        }
+        $sexWhere = [];
+        if (!empty($param['sex'])) {
+            $sexWhere = ['sex'=>$param['sex']];
+        }
+        $total = ActiveApplyModel::hasWhere('user',$sexWhere)->where($where)->count();
+        $this->assign('total', $total);
+
+        $list  = ActiveApplyModel::hasWhere('user',$sexWhere)->with('user')->where($where)->order('check_status asc')->paginate(10, false, [
+            'query' => $param,//不丢失已存在的url参数
+        ]);
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['user']['birthday']);
+        }
+
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 审核
+     */
+    public function checkPost()
+    {
+        $data = $this->request->post();
+        ActiveApplyModel::update($data, ['id' => $data['id']]);
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 报名列表导出
+     */
+    public function applyExport()
+    {
+        $param = $this->request->param();
+
+        $where = [
+            ['active_id', '=', $param['id']],
+        ];
+        if (!empty($param['check_status'])) {
+            $where[] = ['cmf_active_apply.check_status', '=', $param['check_status']];
+        }
+        $sexWhere = [];
+        if (!empty($param['sex'])) {
+            $sexWhere = ['sex'=>$param['sex']];
+        }
+
+        $list  = ActiveApplyModel::hasWhere('user',$sexWhere)->with('user')->where($where)->order('check_status asc')->select();
+        $data = [];
+        foreach ($list as $v) {
+            $data[] = [
+                'name' => $v['user']['realname'],
+                'sex' => $v['user']['sex_text'],
+                'age' => Fun::getAgeByBirth($v['user']['birthday']),
+                'mobile' => $v['user']['mobile'],
+                'company' => $v['user']['company'],
+                'native' => $v['user']['native'],
+                'marry' => $v['user']['marry'],
+                'check_status' => $v['status_text'],
+            ];
+        }
+
+        if (empty($data)) {
+            return '暂无数据';
+        }
+
+        $excel = new Excel();
+        $title = [
+            ['name','姓名'],
+            ['sex','性别'],
+            ['age','年龄'],
+            ['mobile','电话'],
+            ['company','单位'],
+            ['native','籍贯'],
+            ['marry','婚姻状况'],
+            ['check_status','审核状态'],
+        ];
+        $excel->export('报名列表',$title,$data,['mobile']);
+    }
+}

+ 175 - 0
app/love/controller/AdminLotteryController.php

@@ -0,0 +1,175 @@
+<?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: Powerless < wzxaini9@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\love\controller;
+
+use app\love\model\LotteryLogModel;
+use app\love\model\LotteryPrizeModel;
+use app\love\model\UserModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+class AdminLotteryController extends AdminBaseController
+{
+    /**
+     * 列表
+     */
+    public function prize()
+    {
+        $status = $this->request->param('status',1);
+        $where = [];
+        if (!empty($status)) {
+            $where[] = ['status', '=', $status];
+        }
+        $list = LotteryPrizeModel::where($where)->order('id asc')->paginate(10);
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+
+        // 渲染模板输出
+        $this->assign('status', $status);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        return $this->fetch();
+    }
+
+    /**
+     * 添加提交
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+            $post = $data['post'];
+
+            if ($post['odds'] < 0 || $post['odds'] > 100) {
+                $this->error('中奖概率需要0到100之间');
+            }
+            if ($post['number'] < 0) {
+                $this->error('奖品数量不能小于0');
+            }
+
+            LotteryPrizeModel::create($post);
+
+            $this->success('添加成功!', url('prize'));
+        }
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit()
+    {
+        $id   = $this->request->param('id', 0, 'intval');
+        $post = LotteryPrizeModel::get($id);
+
+        $this->assign('post', $post);
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑提交
+     */
+    public function editPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+            $post = $data['post'];
+
+            if ($post['odds'] < 0 || $post['odds'] > 100) {
+                $this->error('中奖概率需要0到100之间');
+            }
+            if ($post['number'] < 0) {
+                $this->error('奖品数量不能小于0');
+            }
+
+            LotteryPrizeModel::update($post, ['id' => $post['id']]);
+
+            $this->success('编辑成功!', url('prize'));
+        }
+    }
+
+    /**
+     * 调整状态
+     */
+    public function setStatus()
+    {
+        $id     = input('param.id', 0, 'intval');
+        $status = input('param.status', 0, 'intval');
+        if (empty($id) || empty($status)) {
+            $this->error('数据有误');
+        }
+
+        LotteryPrizeModel::update(['status' => $status], ['id' => $id]);
+
+        $this->success('操作成功!');
+
+    }
+
+    /**
+     * 记录列表
+     */
+    public function log()
+    {
+        //搜索条件
+        $param  = $this->request->param();
+        $where = [];
+        if (!empty($param['mobile'])) {
+            $user = UserModel::where('mobile', $param['mobile'])->find();
+            if (empty($user)) {
+                $where[] = ['id', '=', 0];
+            } else {
+                $where[] = ['user_id', '=', $user['id']];
+            }
+        }
+        if (!empty($param['status'])) {
+            $where[] = ['status', '=', $param['status']];
+        }
+
+        //列表
+        $list = LotteryLogModel::with('user')->where($where)->order('id asc')->paginate(10, false, [
+            'query' => $param,//不丢失已存在的url参数
+        ]);
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+
+        // 渲染模板输出
+        $this->assign('mobile', isset($param['mobile']) ? $param['mobile'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        return $this->fetch();
+    }
+
+    /**
+     * 领奖
+     */
+    public function receive()
+    {
+        $id = input('param.id', 0, 'intval');
+        LotteryLogModel::update(['status' => 1,'out_at'=>time()], ['id' => $id]);
+
+        //增加领证次数
+        Db::name('config')->where('id',1)->setInc('lottery_num');
+
+        $this->success('操作成功!');
+
+    }
+}

+ 50 - 0
app/love/controller/AdminSelectController.php

@@ -0,0 +1,50 @@
+<?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: Powerless < wzxaini9@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\love\controller;
+
+use app\love\model\UserSelectLogModel;
+use cmf\controller\AdminBaseController;
+
+class AdminSelectController extends AdminBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $list = UserSelectLogModel::with('user1,user2')->order('is_top asc,is_display desc')->paginate(10);
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+
+        // 渲染模板输出
+        return $this->fetch();
+    }
+
+    /**
+     * 置顶
+     */
+    public function top()
+    {
+        $id = $this->request->param('id');
+        if (empty($id)) {
+            $this->error('参数错误');
+        }
+
+        UserSelectLogModel::update(['is_top' => 2], ['is_top' => 1]);
+        UserSelectLogModel::update(['is_top' => 1, 'is_display' => 1], ['id' => $id]);
+        $this->success('操作成功');
+    }
+
+}

+ 123 - 0
app/love/controller/LoginController.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace app\love\controller;
+
+use app\love\model\ActiveApplyModel;
+use app\love\model\ActiveModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserModel;
+use cmf\controller\HomeBaseController;
+use think\facade\Session;
+
+class LoginController extends HomeBaseController
+{
+    /**
+     * 登录
+     */
+    public function index()
+    {
+        $userId = cmf_get_current_user_id();
+
+        $active_id = $this->request->param('active_id', 0);
+        if (!empty($active_id)) {
+            Session::set('active_id', $active_id);
+        }
+
+        if (!empty($userId)) {
+            $user = UserModel::get($userId);
+            if (!empty($user)) {
+                if ($user['user_status'] == 0) {
+                    $this->redirect(cmf_url('love/register/ban'));
+                }
+                //完善资料
+                if ($user['is_complete'] == 2) {
+                    if (empty($user['idcard'])) {
+                        $this->redirect(cmf_url('love/register/idcard'));
+                    } else {
+                        $this->redirect(cmf_url('love/register/intro'));
+                    }
+                }
+
+                if ($active_id) {
+                    $this->redirect(url('love/active/detail') . "?id={$active_id}");
+                } else {
+                    $this->redirect(url('/'));
+                }
+            }
+        }
+
+        $this->redirect('https://www.jucai.gov.cn/api/auth/wechat_auth?url=' . urlencode(url('love/login/wechatBack', '', true, true)));
+    }
+
+    /**
+     * 微信回调
+     */
+    public function wechatBack()
+    {
+        $param   = $this->request->param();
+        $open_id = $param['openid'];
+
+        //登录
+        $user = UserModel::get(['open_id' => $open_id]);
+        if (empty($user)) {
+            $user = UserModel::create([
+                'user_type'   => 2,
+                'open_id'     => $open_id,
+//                'username'    => $param['nickname'],
+//                'avatar'      => $param['headimgurl'],
+                'create_time' => time(),
+            ]);
+            UserMatingModel::create(['user_id' => $user['id']]);
+            //活动
+            $active_id = Session::pull('active_id');
+            if (!empty($active_id)) {
+                $active = ActiveModel::get($active_id);
+                if (!empty($active)) {
+                    ActiveApplyModel::create([
+                        'active_id'    => $active_id,
+                        'user_id'      => $user['id'],
+                        'create_time'  => time(),
+                        'check_status' => 2,
+                        'check_time'   => time(),
+                    ]);
+                }
+            }
+        }
+
+        session('user', $user->toArray());
+        $data = [
+            'last_login_time' => time(),
+            'last_login_ip'   => get_client_ip(0, true),
+        ];
+        UserModel::update($data, ['id' => $user['id']]);
+        $token = cmf_generate_user_token($user["id"], 'mobile');
+        if (!empty($token)) {
+            session('token', $token);
+        }
+
+        $domain = Session::get('domain');
+        $this->redirect($domain ?: '/');
+    }
+
+    public function login1()
+    {
+        $user = UserModel::get(3);
+        session('user', $user->toArray());
+
+        $token = cmf_generate_user_token(3, 'mobile');
+        if (!empty($token)) {
+            session('token', $token);
+        }
+
+        return 'OK';
+    }
+
+    /**
+     * 退出登录
+     */
+    public function logout()
+    {
+        session("user", null);//只有前台用户退出
+        return redirect($this->request->root() . "/");
+    }
+}

+ 143 - 0
app/love/controller/LotteryController.php

@@ -0,0 +1,143 @@
+<?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\love\controller;
+
+use app\common\Wechat;
+use app\love\model\LotteryLogModel;
+use app\love\model\LotteryPrizeModel;
+use app\love\model\UserModel;
+use cmf\controller\HomeBaseController;
+use think\facade\Request;
+
+class LotteryController extends HomeBaseController
+{
+    public function initialize()
+    {
+        parent::initialize();
+
+        //分享页面
+        $wx_api_sign = Wechat::instance()->getJsSign(Request::url(true));
+        $this->assign('domain', $this->request->domain());
+        $this->assign('wx_api_sign', json_encode($wx_api_sign));
+    }
+
+    //抽奖页
+    public function index()
+    {
+        //验证
+        $user_id = cmf_get_current_user_id();
+        $user    = UserModel::get($user_id);
+        if ($user['is_marry'] == 2 || $user['sex'] == 1) {
+            $this->redirect('/');
+        }
+
+        $prize       = LotteryPrizeModel::all();
+        $ids         = [];
+        $restaraunts = [];
+        $colors      = [];
+        $images      = [];
+        foreach ($prize as $v) {
+            $ids[]         = $v->id;
+            $restaraunts[] = $v->name;
+            $colors[]      = $v->bg;
+            $images[]      = cmf_get_image_url($v->image);
+        }
+        $this->assign('prize', $prize);
+        $this->assign('ids', $ids);
+        $this->assign('restaraunts', $restaraunts);
+        $this->assign('colors', $colors);
+        $this->assign('images', $images);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 抽奖
+     */
+    public function lotteryPost()
+    {
+        //抽奖次数
+        $user_id = cmf_get_current_user_id();
+        $user    = UserModel::get($user_id);
+        //已婚
+        if ($user['is_marry'] == 2) {
+            $this->error('已结婚的才能抽奖');
+        }
+        if ($user['sex'] == 1) {
+            $this->error('女生才能抽奖');
+        }
+
+        $lottery = LotteryLogModel::where('user_id', $user_id)->find();
+        if (!empty($lottery)) {
+            $this->error('一个人只能抽一次奖',url('log'));
+        }
+
+        //奖品列表
+        $prize_arr = LotteryPrizeModel::where('number', '>', 0)->select();
+        $arr       = [];
+        foreach ($prize_arr as $key => $val) {
+            $arr[$val['id']] = $val['odds'] * 100;
+        }
+        $rid = $this->_get_rand($arr); //根据概率获取奖项id
+
+        //增加奖品
+        $prize = LotteryPrizeModel::where('id', $rid)->find();
+        $prize->number--;
+        $prize->save();
+
+        LotteryLogModel::create([
+            'user_id'     => $user_id,
+            'prize_id'    => $rid,
+            'create_time' => time(),
+            'prize_name'  => $prize['name'],
+        ]);
+
+        $this->success('恭喜你,获得' . $prize['name'],'',['id' => $rid]);
+    }
+
+    /**
+     * 中奖记录
+     */
+    public function log()
+    {
+        //奖品
+        $user_id = cmf_get_current_user_id();
+        $lottery = LotteryLogModel::with('lottery')->where('user_id',$user_id)->find();
+        if (empty($lottery) || $lottery['status'] == 1) {
+            $this->redirect('/');
+        }
+        $lottery['lottery']['image'] = cmf_get_image_url($lottery['lottery']['image']);
+        $this->assign('lottery',$lottery);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 获取奖品
+     */
+    private function _get_rand($proArr)
+    {
+        $result = '';
+        //概率数组的总概率精度
+        $proSum = array_sum($proArr);
+        //概率数组循环
+        foreach ($proArr as $key => $proCur) {
+            $randNum = mt_rand(1, $proSum);
+            if ($randNum <= $proCur) {
+                $result = $key;
+                break;
+            } else {
+                $proSum -= $proCur;
+            }
+        }
+        return $result;
+    }
+}

+ 75 - 0
app/love/controller/LoveBaseController.php

@@ -0,0 +1,75 @@
+<?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: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+namespace app\love\controller;
+
+use app\common\Wechat;
+use app\love\model\LotteryLogModel;
+use app\love\model\UserModel;
+use cmf\controller\HomeBaseController;
+use think\facade\Request;
+use think\facade\Session;
+
+class LoveBaseController extends HomeBaseController
+{
+    protected $user = null;
+    protected $mating = null;
+
+    public function initialize()
+    {
+        parent::initialize();
+        $userId = cmf_get_current_user_id();
+
+        //记录回调
+        $domain = $this->request->url(true);
+        Session::set('domain', $domain);
+
+        if (empty($userId)) {
+            if ($this->request->isAjax()) {
+                $this->error("您尚未登录", cmf_url("love/Login/index"));
+            } else {
+                $this->redirect(cmf_url("love/Login/index"));
+            }
+        }
+
+        $this->user = UserModel::get($userId);
+        if (empty($this->user)) {
+            Session::destroy();
+            $this->redirect(cmf_url("love/Login/index"));
+        }
+        //拉黑
+        if ($this->user['user_status'] == 0) {
+            $this->redirect(cmf_url('love/register/ban'));
+        }
+        //完善资料
+        if ($this->user['is_complete'] == 2) {
+            if (empty($this->user['sex'])) {
+                $this->redirect(cmf_url('love/register/sex'));
+            } else {
+                $this->redirect(cmf_url('love/register/intro'));
+            }
+        }
+
+        //分享页面
+        $wx_api_sign = Wechat::instance()->getJsSign(Request::url(true));
+        $this->assign('domain', $this->request->domain());
+        $this->assign('wx_api_sign', json_encode($wx_api_sign));
+    }
+
+    /**
+     * 会员状态
+     */
+    protected function checkStatus()
+    {
+        if ($this->user['check_status'] != 2) {
+            $this->error('请等待审核!');
+        }
+    }
+}

+ 331 - 0
app/love/controller/MessageController.php

@@ -0,0 +1,331 @@
+<?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\love\controller;
+
+use app\love\model\UserFriendModel;
+use app\love\model\UserInviteModel;
+use app\love\model\UserMessageModel;
+use app\love\model\UserModel;
+
+class MessageController extends LoveBaseController
+{
+    /**
+     * 好友列表
+     */
+    public function index()
+    {
+        $user_id = cmf_get_current_user_id();
+        $list    = UserFriendModel::with('friend')->where('user_id', $user_id)->select();
+        foreach ($list as $v) {
+            $v['last_msg_time_text'] = date('m-d H:i', $v['last_msg_time']);
+        }
+        $this->assign('list', $list);
+
+        //未读消息数
+        $unread_num = UserFriendModel::where('user_id', $user_id)->sum('unread_num');
+
+        //收到邀请数
+        $invite_num = UserInviteModel::where('to_id', $user_id)
+            ->where('status', 1)
+            ->count();
+        $this->assign('unread_num', $unread_num + $invite_num);
+        $this->assign('invite_num', $invite_num);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 聊天页
+     */
+    public function detail()
+    {
+        //错误判断
+        $id = $this->request->get('id');
+        if (empty($id)) {
+            $this->error('该用户不存在');
+        }
+        $my_id = cmf_get_current_user_id();
+        if ($id == $my_id) {
+            $this->error('不能和自己对话');
+        }
+        $user = UserModel::get($id);
+        if (empty($user)) {
+            $this->error('该用户不存在');
+        }
+        $this->assign('user', $user);
+        $this->assign('to_id', $id);
+        $this->assign('from_id', $my_id);
+
+        //好友
+        $friend = UserFriendModel::get(['user_id' => $my_id, 'friend_id' => $id]);
+        if (empty($friend)) {
+            $this->error('请等待对方通过聊天邀请');
+        } else {
+            $friend->unread_num = 0;
+            $friend->save();
+        }
+
+        //获取消息
+        $message = UserMessageModel::with('toUser,fromUser')
+            ->where(function ($query) use ($id, $my_id) {
+                $query->where(function ($query) use ($id, $my_id) {
+                    $query->where([
+                        ['from_id', '=', $id],
+                        ['to_id', '=', $my_id],
+                    ]);
+                })->whereOr(
+                    function ($query) use ($id, $my_id) {
+                        $query->where([
+                            ['from_id', '=', $my_id],
+                            ['to_id', '=', $id],
+                        ]);
+                    });
+            })->order('create_time desc')->limit(10)->select();
+        $this->assign('last_message_time', $message->isEmpty() ? 0 : $message[0]['create_time']);
+        foreach ($message as $v) {
+            $v['create_time_text'] = date('m-d H:i', $v['create_time']);
+        }
+        $message = $message->reverse();
+        $this->assign('first_message_time', $message->isEmpty() ? time() : $message[0]['create_time']);
+        $this->assign('message', $message);
+
+        //更多
+        $count = UserMessageModel::with('toUser,fromUser')
+            ->where([
+                ['from_id', '=', $id],
+                ['to_id', '=', $my_id],
+            ])->whereOr([
+                ['from_id', '=', $my_id],
+                ['to_id', '=', $id],
+            ])->count();
+        $this->assign('has_more', $count > 10 ? 'true' : 'false');
+
+        return $this->fetch();
+    }
+
+    /**
+     * 发送消息
+     */
+    public function sendMessage()
+    {
+        $data    = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $time    = time();
+
+        //增加聊天记录
+        UserMessageModel::create([
+            'from_id'     => $user_id,
+            'to_id'       => $data['to_id'],
+            'message'     => $data['message'],
+            'create_time' => $time,
+        ]);
+
+        //增加最后聊天消息
+        UserFriendModel::update(['last_msg' => '我:' . $data['message'], 'last_msg_time' => $time], ['user_id' => $user_id, 'friend_id' => $data['to_id']]);
+        $friend                = UserFriendModel::get(['user_id' => $data['to_id'], 'friend_id' => $user_id]);
+        $friend->last_msg      = '对方:' . $data['message'];
+        $friend->last_msg_time = $time;
+        $friend->unread_num++;
+        $friend->save();
+
+        //获取消息
+        $message = UserMessageModel::with('toUser,fromUser')
+            ->where(function ($query) use ($user_id, $data) {
+                $query->where(function ($query) use ($user_id, $data) {
+                    $query->where([
+                        ['from_id', '=', $user_id],
+                        ['to_id', '=', $data['to_id']],
+                    ]);
+                })->whereOr(
+                    function ($query) use ($user_id, $data) {
+                        $query->where([
+                            ['from_id', '=', $data['to_id']],
+                            ['to_id', '=', $user_id],
+                        ]);
+                    });
+            })->where(
+                function ($query) use ($data) {
+                    $query->where('create_time', '>', $data['message_time']);
+                })
+            ->order('create_time asc')->select();
+
+        $this->result($message, 1);
+    }
+
+    /**
+     * 获取消息
+     */
+    public function getMessage()
+    {
+        $data    = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+
+        //获取消息
+        $message = UserMessageModel::with('toUser,fromUser')
+            ->where(function ($query) use ($user_id, $data) {
+                $query->where(function ($query) use ($user_id, $data) {
+                    $query->where([
+                        ['from_id', '=', $user_id],
+                        ['to_id', '=', $data['to_id']],
+                    ]);
+                })->whereOr(
+                    function ($query) use ($user_id, $data) {
+                        $query->where([
+                            ['from_id', '=', $data['to_id']],
+                            ['to_id', '=', $user_id],
+                        ]);
+                    });
+            })->where(
+                function ($query) use ($data) {
+                    $query->where('create_time', '>', $data['message_time']);
+                })
+            ->order('create_time asc')->select();
+
+        $this->result($message, 1);
+    }
+
+    /**
+     * 更多记录
+     */
+    public function moreMessage()
+    {
+        $data    = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+
+        //获取消息
+        $list = UserMessageModel::with('toUser,fromUser')
+            ->where(function ($query) use ($user_id, $data) {
+                $query->where(function ($query) use ($user_id, $data) {
+                    $query->where([
+                        ['from_id', '=', $user_id],
+                        ['to_id', '=', $data['to_id']],
+                    ]);
+                })->whereOr(
+                    function ($query) use ($user_id, $data) {
+                        $query->where([
+                            ['from_id', '=', $data['to_id']],
+                            ['to_id', '=', $user_id],
+                        ]);
+                    });
+            })->where(
+                function ($query) use ($data) {
+                    $query->where('create_time', '<', $data['message_time']);
+                })
+            ->order('create_time desc')->limit(10)->select();
+        foreach ($list as $v) {
+            $v['create_time_text'] = date('m-d H:i', $v['create_time']);
+        }
+        $list = $list->reverse();
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 邀请
+     */
+    public function invite()
+    {
+        $list = UserInviteModel::with('from_user')
+            ->where('to_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('status asc,create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']        = $v['from_user'];
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+        $this->assign('list1', $list);
+
+        $list = UserInviteModel::with('to_user')
+            ->where('from_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('status asc,create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']        = $v['to_user'];
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+        $this->assign('list2', $list);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 邀请列表
+     */
+    public function inviteList()
+    {
+        $param   = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $where   = [];
+        $with    = 'from_user';
+        if ($param['status'] == 0) {
+            $where[] = ['to_id', '=', $user_id];
+        } elseif ($param['status'] == 1) {
+            $where[] = ['from_id', '=', $user_id];
+            $with    = 'to_user';
+        }
+
+        $list = UserInviteModel::with($with)
+            ->where($where)
+            ->page($param['page'])
+            ->limit(10)
+            ->order('status asc,create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            if ($param['status'] == 0) {
+                $v['user'] = $v['from_user'];
+            } elseif ($param['status'] == 1) {
+                $v['user'] = $v['to_user'];
+            }
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 处理邀请
+     */
+    public function dealInvite()
+    {
+        $post    = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $info    = UserInviteModel::get(['from_id' => $post['id'], 'to_id' => $user_id]);
+        if (empty($info)) {
+            $this->error('请刷新页面');
+        }
+
+        //更改状态
+        $info->status = $post['status'];
+        $info->save();
+
+        //增加好友
+        if ($post['status'] == 2) {
+            UserFriendModel::create([
+                'user_id'       => $post['id'],
+                'friend_id'     => $user_id,
+                'last_msg_time' => time(),
+            ]);
+            UserFriendModel::create([
+                'user_id'       => $user_id,
+                'friend_id'     => $post['id'],
+                'last_msg_time' => time(),
+            ]);
+            $this->error('', url('detail') . '?id=' . $post['id']);
+        } else {
+            $this->success('');
+        }
+    }
+}

+ 857 - 0
app/love/controller/MyController.php

@@ -0,0 +1,857 @@
+<?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\love\controller;
+
+use app\common\Constant;
+use app\common\Fun;
+use app\love\model\LotteryPrizeModel;
+use app\love\model\UserFavoriteModel;
+use app\love\model\UserFriendModel;
+use app\love\model\UserGiftModel;
+use app\love\model\UserInviteModel;
+use app\love\model\UserMarryModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserMessageModel;
+use app\love\model\UserModel;
+use app\love\model\UserSelectLogModel;
+use app\love\model\UserSelectModel;
+use app\love\model\UserVisitModel;
+use think\Db;
+
+class MyController extends LoveBaseController
+{
+    private $base_arr = ['nation', 'company', 'job', 'hobby', 'smoke', 'drink'];
+
+    /**
+     * 我的
+     */
+    public function index()
+    {
+        //用户信息
+        $user_id = cmf_get_current_user_id();
+        $this->assign('user_id', $user_id);
+        $user = UserModel::get($user_id);
+        unset($user['user_pass']);
+        $this->assign('user', json_encode($user));
+
+        //完善资料
+        $is_perfect = 'false';
+        foreach ($this->base_arr as $v) {
+            if (empty($user[$v])) {
+                $is_perfect = 'true';
+                break;
+            }
+        }
+        $this->assign('is_perfect', $is_perfect);
+
+        //择偶
+//        $mating    = UserMatingModel::get(['user_id' => cmf_get_current_user_id()]);
+        $is_mating = 'false';
+        /*foreach ($this->mating_arr as $v) {
+            if (empty($mating[$v])) {
+                $is_mating = 'true';
+                break;
+            }
+        }*/
+        $this->assign('is_mating', $is_mating);
+
+        //未读消息数
+        $unread_num = UserFriendModel::where('user_id', $user_id)->sum('unread_num');
+        $invite_num = UserInviteModel::where('to_id', $user_id)
+            ->where('status', 1)
+            ->count();
+        $this->assign('unread_num', $unread_num + $invite_num);
+
+        //我的访客
+        $visit_where = [
+            ['to_id', '=', $user_id],
+            ['status', '=', 2],
+        ];
+        $visit_count = UserVisitModel::where($visit_where)->count();
+        $this->assign('visit_count', $visit_count ?: '');
+
+        //收到的礼物
+        $gift_count = UserGiftModel::where($visit_where)->count();
+        $this->assign('gift_count', $gift_count ?: '');
+
+        //收到的选择
+        $select_count = UserSelectModel::where([
+            ['uid', '=', $user_id],
+            ['status', '=', 2],
+        ])->count();
+        $this->assign('select_count', $select_count ?: '');
+
+        //是否互选
+        $select_log = 'false';
+        if ($this->user['sex'] == 1 && $this->user['use_ticket'] == 2) {
+            $log_check = UserSelectLogModel::where('user_id1', $this->user['id'])->find();
+            if (!empty($log_check)) {
+                $select_log = 'true';
+            }
+        }
+        $this->assign('select_log', $select_log);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 审核
+     */
+    public function check()
+    {
+        //用户信息
+        $user = UserModel::get(cmf_get_current_user_id());
+        unset($user['user_pass']);
+        $this->assign('user', json_encode($user));
+
+        return $this->fetch();
+    }
+
+    /**
+     * 个人资料
+     */
+    public function profile()
+    {
+        //用户信息
+        $user             = UserModel::get(cmf_get_current_user_id());
+        $user['birthday'] = date('Y-m-d', $user['birthday']);
+        unset($user['user_pass']);
+        $this->assign('user', json_encode($user));
+
+        $data = [
+            'marry'     => Constant::MARRY,
+            'high'      => Constant::HIGH,
+            'weight'    => Constant::WEIGHT,
+            'education' => Constant::EDUCATION,
+            'income'    => Constant::INCOME,
+            'nation'    => Constant::NATION,
+            'smoke'     => Constant::SMOKE,
+            'drink'     => Constant::DRINK,
+            'family'    => Constant::FAMILY,
+            'tinyint'   => Constant::TINYINT,
+        ];
+        foreach ($data as &$v) {
+            $v = json_encode($v);
+            unset($v);
+        }
+
+        return $this->fetch('', $data);
+    }
+
+    /**
+     * 个人资料提交
+     */
+    public function profilePost()
+    {
+        $param                  = $this->request->post();
+        $param['user_nickname'] = $param['realname'];
+        $param['birthday']      = strtotime($param['birthday']);
+        UserModel::update($param, ['id' => cmf_get_current_user_id()]);
+        $this->success('操作成功');
+    }
+
+    /**
+     * 择偶要求
+     */
+    public function cond()
+    {
+        $mating = UserMatingModel::get(['user_id' => cmf_get_current_user_id()]);
+        $this->assign('mating', $mating);
+
+        $education   = Constant::EDUCATION;
+        $education[] = '无要求';
+        $data        = [
+            'min_age'    => Constant::MIN_AGE,
+            'max_age'    => Constant::MAX_AGE,
+            'min_high'   => Constant::MIN_HIGH,
+            'max_high'   => Constant::MAX_HIGH,
+            'min_weight' => Constant::MIN_WEIGHT,
+            'max_weight' => Constant::MAX_WEIGHT,
+            'native'     => Constant::NATIVE,
+            'education'  => $education,
+            'income'     => Constant::COND_INCOME,
+            'smoke'      => Constant::SMOKE,
+            'drink'      => Constant::DRINK,
+            'tinyint'    => Constant::COND_TINYINT,
+        ];
+        foreach ($data as &$v) {
+            $v = json_encode($v);
+            unset($v);
+        }
+
+        return $this->fetch('', $data);
+    }
+
+    /**
+     * 择偶要求提交
+     */
+    public function condPost()
+    {
+        $param = $this->request->post();
+        UserMatingModel::update($param, ['user_id' => cmf_get_current_user_id()]);
+        $this->success('操作成功');
+    }
+
+    /**
+     * 自我介绍
+     */
+    public function intro()
+    {
+        $user = UserModel::field('main_image,main_image_thumb,signature')->where('id', cmf_get_current_user_id())->find();
+        $this->assign('user', json_encode($user));
+
+        return $this->fetch();
+    }
+
+    /**
+     * 自我介绍提交
+     */
+    public function introPost()
+    {
+        $param           = $this->request->post();
+        $param['avatar'] = $param['main_image_thumb'];
+        UserModel::update($param, ['id' => cmf_get_current_user_id()], ['avatar', 'main_image', 'main_image_thumb', 'signature']);
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 相册
+     */
+    public function photo()
+    {
+        $user = UserModel::get(cmf_get_current_user_id());
+        if (empty($user['more'])) {
+            $user['more'] = [];
+        }
+        $more = array_filter($user['more']);
+        $this->assign('more', json_encode($more));
+
+        return $this->fetch();
+    }
+
+    /**
+     * 相册提交
+     */
+    public function photoPost()
+    {
+        $param = $this->request->post();
+        if (empty($param)) {
+            $param = ['more' => ''];
+        }
+        UserModel::update($param, ['id' => cmf_get_current_user_id()], 'more');
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 我的访客
+     */
+    public function visitme()
+    {
+        //清除未读
+        UserVisitModel::update(['status' => 1], ['to_id' => cmf_get_current_user_id(), 'status' => 2]);
+
+        $list = UserVisitModel::with('to_user')
+            ->where('from_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('last_visit_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']            = $v['to_user'];
+            $v['user']['age']     = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['last_visit_time'] = date('m-d H:i', $v['last_visit_time']);
+        }
+        $this->assign('list1', $list);
+
+        $list = UserVisitModel::with('from_user')
+            ->where('to_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('last_visit_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']            = $v['from_user'];
+            $v['user']['age']     = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['last_visit_time'] = date('m-d H:i', $v['last_visit_time']);
+        }
+        $this->assign('list2', $list);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 我的访客列表
+     */
+    public function visitmeList()
+    {
+        $param   = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $where   = [];
+        $with    = 'to_user';
+        if ($param['status'] == 0) {
+            $where[] = ['from_id', '=', $user_id];
+        } elseif ($param['status'] == 1) {
+            $where[] = ['to_id', '=', $user_id];
+            $with    = 'from_user';
+        }
+
+        $list = UserVisitModel::with($with)
+            ->where($where)
+            ->page($param['page'])
+            ->limit(10)
+            ->order('last_visit_time desc')
+            ->select();
+        foreach ($list as $v) {
+            if ($param['status'] == 0) {
+                $v['user'] = $v['to_user'];
+            } elseif ($param['status'] == 1) {
+                $v['user'] = $v['from_user'];
+            }
+            $v['user']['age']     = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['last_visit_time'] = date('m-d H:i', $v['last_visit_time']);
+        }
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 我的收藏
+     */
+    public function star()
+    {
+        $list = UserFavoriteModel::with('to_user')
+            ->where('user_id', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['to_user']['age'] = empty($v['to_user']['birthday']) ? 0 : date('Y') - date('Y', $v['to_user']['birthday']);
+            $v['create_time']    = date('m-d H:i', $v['create_time']);
+        }
+        $this->assign('list', $list);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 我的收藏列表
+     */
+    public function starList()
+    {
+        $param = $this->request->post();
+
+        $list = UserFavoriteModel::with('to_user')
+            ->where('user_id', cmf_get_current_user_id())
+            ->page($param['page'])
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['to_user']['age'] = empty($v['to_user']['birthday']) ? 0 : date('Y') - date('Y', $v['to_user']['birthday']);
+            $v['create_time']    = date('m-d H:i', $v['create_time']);
+        }
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 我的互选
+     */
+    public function select()
+    {
+        //清除未读
+        $user_id = cmf_get_current_user_id();
+        UserSelectModel::update(['status' => 1], ['uid' => $user_id, 'status' => 2]);
+
+        $select = UserSelectModel::where('user_id|uid', '=', $user_id)
+            ->where('is_confirm', 1)
+            ->find();
+        //奖品
+        $has_prize   = LotteryPrizeModel::all();
+        $this->assign('has_prize', $has_prize->isEmpty() ? 'false' : 'true');
+        if (!empty($select)) {
+            //已选择
+            if ($select['user_id'] == $user_id) {
+                $uid = $select['uid'];
+            } else {
+                $uid = $select['user_id'];
+            }
+            $user        = UserModel::get($uid);
+            $user['age'] = Fun::getAgeByBirth($user['birthday']);
+            $this->assign('user', $user);
+            $this->assign('list1', '[]');
+            $this->assign('list2', '[]');
+            $this->assign('is_select', 'true');
+        } else {
+            //未选择
+            $this->assign('user', '[]');
+            $list = UserSelectModel::with('to_user')
+                ->where('uid', '=', cmf_get_current_user_id())
+                ->limit(10)
+                ->order('create_time desc')
+                ->select();
+            foreach ($list as $v) {
+                $v['user']        = $v['to_user'];
+                $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+                $v['create_time'] = date('m-d H:i', $v['create_time']);
+            }
+            $this->assign('list1', $list);
+
+            $list = UserSelectModel::with('from_user')
+                ->where('user_id', '=', cmf_get_current_user_id())
+                ->limit(10)
+                ->order('create_time desc')
+                ->select();
+            foreach ($list as $v) {
+                $v['user']        = $v['from_user'];
+                $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+                $v['create_time'] = date('m-d H:i', $v['create_time']);
+            }
+            $this->assign('list2', $list);
+            $this->assign('is_select', 'false');
+        }
+
+        return $this->fetch();
+    }
+
+    /**
+     * 我的访客列表
+     */
+    public function selectList()
+    {
+        $param   = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $where   = [];
+        $with    = 'to_user';
+        if ($param['status'] == 0) {
+            $where[] = ['user_id', '=', $user_id];
+        } elseif ($param['status'] == 1) {
+            $where[] = ['uid', '=', $user_id];
+            $with    = 'from_user';
+        }
+
+        $list = UserSelectModel::with($with)
+            ->where($where)
+            ->page($param['page'])
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            if ($param['status'] == 0) {
+                $v['user'] = $v['to_user'];
+            } elseif ($param['status'] == 1) {
+                $v['user'] = $v['from_user'];
+            }
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 取消选择
+     */
+    public function selectCancel()
+    {
+        $id      = $this->request->post('id');
+        $user_id = cmf_get_current_user_id();
+        $user    = UserSelectModel::get(['user_id' => $user_id, 'uid' => $id]);
+        //20230423增加两个userid对调的查询,因为双方都有权利取消
+        if (empty($user)) {
+            $user = UserSelectModel::get(['user_id' => $id, 'uid' => $user_id]);
+        }
+        if (!empty($user)) {
+            UserSelectLogModel::where('user_id1|user_id2', $id)->update(['delete_time'=>time()]);
+            $user->delete();
+        }
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 确认选择
+     */
+    public function selectConfirm()
+    {
+        $id      = $this->request->post('id');
+        $user_id = cmf_get_current_user_id();
+        if ($user_id == $id) {
+            $this->error('不可以选择自己!');
+        }
+        $user  = UserSelectModel::get(['user_id' => $id, 'uid' => $user_id]);
+        $check = UserSelectLogModel::where('user_id1|user_id2', $id)->find();
+        if (!empty($check)) {
+            $this->error('对方已被选择!');
+        }
+        if (empty($user)) {
+            $this->error('该数据不存在!');
+        }
+        $user->is_confirm   = 1;
+        $user->confirm_time = time();
+        $user->save();
+
+        //增加互选记录
+        $log = ['confirm_time' => time()];
+        if ($this->user['sex'] == 1) {
+            $log['user_id1'] = $user_id;
+            $log['user_id2'] = $id;
+        } else {
+            $log['user_id1'] = $id;
+            $log['user_id2'] = $user_id;
+        }
+        UserSelectLogModel::create($log);
+
+        //查看是否为好友
+        $friend = UserFriendModel::where([
+            ['user_id', '=', $user_id],
+            ['friend_id', '=', $id],
+        ])->find();
+        if (empty($friend)) {
+            //查看是否发起过聊天申请
+            $apply = UserInviteModel::where(function ($query) use ($user_id, $id) {
+                $query->where([
+                    ['from_id', '=', $user_id],
+                    ['to_id', '=', $id],
+                ]);
+            })->whereOr(function ($query) use ($user_id, $id) {
+                $query->where([
+                    ['to_id', '=', $user_id],
+                    ['from_id', '=', $id],
+                ]);
+            })->find();
+            if (!empty($apply)) {
+                $apply->status = 2;
+            }
+            //添加好友
+            UserFriendModel::create([
+                'user_id'       => $id,
+                'friend_id'     => $user_id,
+                'last_msg_time' => time(),
+            ]);
+            UserFriendModel::create([
+                'user_id'       => $user_id,
+                'friend_id'     => $id,
+                'last_msg_time' => time(),
+            ]);
+        }
+
+        //增加互选次数
+        Db::name('config')->where('id', 1)->setInc('select_num');
+        //发送提示
+        $form_id = $this->user->sex = 1 ? $user_id : $id;
+        $to_id = $this->user->sex = 1 ? $id : $user_id;
+        $time = time();
+        UserMessageModel::create([
+            'from_id'     => $form_id,
+            'to_id'       => $to_id,
+            'message'     => '【系统消息】系统赠送了两张咖啡券,约个时间一起去喝咖啡吧。',
+            'create_time' => $time,
+        ]);
+
+        //增加最后聊天消息
+        UserFriendModel::update(['last_msg' => '我:【系统消息】系统赠送了两张咖啡券,约个时间一起去喝咖啡吧。', 'last_msg_time' => $time], ['user_id' => $form_id, 'friend_id' => $to_id]);
+        $friend                = UserFriendModel::get(['user_id' => $to_id, 'friend_id' => $form_id]);
+        $friend->last_msg      = '对方:【系统消息】系统赠送了两张咖啡券,约个时间一起去喝咖啡吧。';
+        $friend->last_msg_time = $time;
+        $friend->unread_num++;
+        $friend->save();
+
+        $other = UserModel::get($id);
+        $this->success('操作成功', '', ['mobile' => $other['mobile']]);
+    }
+
+    /**
+     * 结婚
+     */
+    public function marry()
+    {
+        $user_id = cmf_get_current_user_id();
+        $log     = UserSelectLogModel::where('user_id1|user_id2', $user_id)->find();
+        if (empty($log)) {
+            $this->error('请先确认关系后再结婚!');
+        }
+
+        $id    = $log['user_id1'] == $user_id ? $log['user_id2'] : $log['user_id1'];
+        $marry = UserMarryModel::where(function ($query) use ($user_id, $id) {
+            $query->where([
+                ['user_id1', '=', $user_id],
+                ['user_id2', '=', $id],
+            ]);
+        })->whereOr(function ($query) use ($user_id, $id) {
+            $query->where([
+                ['user_id2', '=', $user_id],
+                ['user_id1', '=', $id],
+            ]);
+        })->find();
+        //20230423增加两个userid对调的查询,因为双方都有权利取消
+        if (empty($marry)) {
+            UserMarryModel::create(['user_id1' => $user_id, 'user_id2' => $id, 'create_time' => time()]);
+            UserModel::where('id', 'in', [$id, $user_id])->update(['is_marry' => 1]);
+        }
+
+        //增加结婚次数
+        Db::name('config')->where('id', 1)->setInc('marry_num');
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 收到的礼物
+     */
+    public function gift()
+    {
+        //清除未读
+        UserGiftModel::update(['status' => 1], ['to_id' => cmf_get_current_user_id(), 'status' => 2]);
+
+        $list = UserGiftModel::with('gift,from_user')
+            ->where('to_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']        = $v['from_user'];
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+        $this->assign('list1', $list);
+
+        $list = UserGiftModel::with('gift,to_user')
+            ->where('from_id', '=', cmf_get_current_user_id())
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            $v['user']        = $v['to_user'];
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+        $this->assign('list2', $list);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 收到的礼物列表
+     */
+    public function giftList()
+    {
+        $param   = $this->request->post();
+        $user_id = cmf_get_current_user_id();
+        $where   = [];
+        $with    = 'gift,from_user';
+        if ($param['status'] == 0) {
+            $where[] = ['to_id', '=', $user_id];
+        } elseif ($param['status'] == 1) {
+            $where[] = ['from_id', '=', $user_id];
+            $with    = 'gift,to_user';
+        }
+
+        $list = UserGiftModel::with($with)
+            ->where($where)
+            ->page($param['page'])
+            ->limit(10)
+            ->order('create_time desc')
+            ->select();
+        foreach ($list as $v) {
+            if ($param['status'] == 0) {
+                $v['user'] = $v['from_user'];
+            } elseif ($param['status'] == 1) {
+                $v['user'] = $v['to_user'];
+            }
+            $v['user']['age'] = empty($v['user']['birthday']) ? 0 : date('Y') - date('Y', $v['user']['birthday']);
+            $v['create_time'] = date('m-d H:i', $v['create_time']);
+        }
+
+        $this->result($list, 1);
+    }
+
+    /**
+     * 图片上传
+     */
+    public function imageUpload()
+    {
+        $file = $this->request->post('file');
+        $ext  = pathinfo($this->request->post('name'))['extension'];
+        if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
+            $this->error('文件后缀必须为jpg,jpeg,png');
+        }
+
+        if ($file) {
+            //创建目录
+            $upload_dir = WEB_ROOT . 'upload' . '/' . 'image';
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+            $upload_dir = $upload_dir . '/' . date('Ymd');
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+
+            //保存文件
+            $file_name = $this->_file_name($ext);
+            $file_path = $upload_dir . '/' . $file_name;
+            $is_upload = $this->_base64_image_content($file, $file_path);
+            if (!$is_upload) {
+                $this->error('上传失败,请重新上传');
+            }
+
+            //缩略图
+            $thumb_file = $this->_file_name($ext);
+            $image      = \think\Image::open($file_path);
+            $image->thumb(220, 270)->save($upload_dir . '/' . $thumb_file);
+
+            $main_image       = cmf_get_user_avatar_url('image' . '/' . date('Ymd') . '/' . $file_name);
+            $main_image_thumb = cmf_get_user_avatar_url('image' . '/' . date('Ymd') . '/' . $thumb_file);
+
+            $this->result([
+                'main_image'       => cmf_get_user_avatar_url($main_image),
+                'main_image_thumb' => cmf_get_user_avatar_url($main_image_thumb),
+            ], 1);
+        } else {
+            $this->error('请上传文件');
+        }
+    }
+
+    /**
+     * 用户头像上传
+     */
+    public function avatarUpload()
+    {
+        $file = $this->request->post('file');
+        $ext  = pathinfo($this->request->post('name'))['extension'];
+        if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
+            $this->error('文件后缀必须为jpg,jpeg,png');
+        }
+
+        if ($file) {
+            //创建目录
+            $upload_dir = WEB_ROOT . 'upload' . '/' . 'avatar';
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+            $upload_dir = $upload_dir . '/' . date('Ymd');
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+
+            //保存文件
+            $file_name = $this->_file_name($ext);
+            $is_upload = $this->_base64_image_content($file, $upload_dir . '/' . $file_name);
+            if (!$is_upload) {
+                $this->error('上传失败,请重新上传');
+            }
+
+            //更新头像
+            $avatar = cmf_get_user_avatar_url('avatar' . '/' . date('Ymd') . '/' . $file_name);
+            UserModel::update(['avatar' => $avatar], ['id' => cmf_get_current_user_id()]);
+
+            $this->result(cmf_get_user_avatar_url($avatar), 1);
+        } else {
+            $this->error('请上传文件');
+        }
+    }
+
+    /**
+     * 用户相册上传
+     */
+    public function photoUpload()
+    {
+        $file = $this->request->post('file');
+        $ext  = pathinfo($this->request->post('name'))['extension'];
+        if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
+            $this->error('文件后缀必须为jpg,jpeg,png');
+        }
+
+        if ($file) {
+            //创建目录
+            $upload_dir = WEB_ROOT . 'upload' . '/' . 'photo';
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+            $upload_dir = $upload_dir . '/' . date('Ymd');
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+
+            //保存文件
+            $file_name = $this->_file_name($ext);
+            $is_upload = $this->_base64_image_content($file, $upload_dir . '/' . $file_name);
+            if (!$is_upload) {
+                $this->error('上传失败,请重新上传');
+            }
+
+            $photo = cmf_get_user_avatar_url('photo' . '/' . date('Ymd') . '/' . $file_name);
+            $this->result(cmf_get_user_avatar_url($photo), 1);
+        } else {
+            $this->error('请上传文件');
+        }
+    }
+
+    /**
+     * 咖啡券
+     */
+    public function ticket()
+    {
+        //是否互选
+        if ($this->user['sex'] == 2) {
+            $this->error('暂无咖啡券', url('index'));
+        }
+        $log_check = UserSelectLogModel::where('user_id1', $this->user['id'])->find();
+        if (empty($log_check)) {
+            $this->error('暂无咖啡券', url('index'));
+        }
+
+        return $this->fetch();
+    }
+
+    public function useTicket()
+    {
+        $this->user->use_ticket = 1;
+        $this->user->save();
+
+        $this->success('核销成功!');
+    }
+
+    private function _file_name($ext)
+    {
+        //生成随机文件名
+        //定义一个包含大小写字母数字的字符串
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        //把字符串分割成数组
+        $newchars = str_split($chars);
+        //打乱数组
+        shuffle($newchars);
+        //从数组中随机取出15个字符
+        $chars_key = array_rand($newchars, 15);
+        //把取出的字符重新组成字符串
+        $fnstr = '';
+        for ($i = 0; $i < 15; $i++) {
+            $fnstr .= $newchars[$chars_key[$i]];
+        }
+        //输出文件名并做MD5加密
+        return md5($fnstr . microtime(true) * 1000) . '.' . $ext;
+    }
+
+    private function _base64_image_content($base64_image_content, $file_path)
+    {
+        //匹配出图片的格式
+        if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)) {
+            if (file_put_contents($file_path, base64_decode(str_replace($result[1], '', $base64_image_content)))) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+}

+ 399 - 0
app/love/controller/RegisterController.php

@@ -0,0 +1,399 @@
+<?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\love\controller;
+
+use app\common\Constant;
+use app\common\Fun;
+use app\common\Wechat;
+use app\love\model\ActiveModel;
+use app\love\model\UserAuthModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserModel;
+use cmf\controller\HomeBaseController;
+use think\facade\Request;
+
+class RegisterController extends HomeBaseController
+{
+    public function initialize()
+    {
+        parent::initialize();
+
+        /*$user_id = cmf_get_current_user_id();
+        if (!empty($user_id)) {
+            $user = UserModel::get($user_id);
+            if ($user['user_status'] > 0 && $user['is_complete'] == 1) {
+                $this->redirect(url('/'));
+            }
+        }*/
+
+        //分享页面
+        $wx_api_sign = Wechat::instance()->getJsSign(Request::url(true));
+        $this->assign('domain', $this->request->domain());
+        $this->assign('wx_api_sign', json_encode($wx_api_sign));
+    }
+
+    /**
+     * 禁用
+     */
+    public function ban()
+    {
+        return $this->fetch();
+    }
+
+    public function marry()
+    {
+        return $this->fetch();
+    }
+
+    public function audit()
+    {
+        return $this->fetch();
+    }
+
+    public function idcard()
+    {
+        $user = UserModel::get(cmf_get_current_user_id());
+        if ($this->request->isPost()) {
+            $idcard = $this->request->post('idcard');
+            if (empty($idcard)) {
+                $this->error('请输入身份证号');
+            }
+
+            $auth = UserAuthModel::where('idcard', $idcard)->find();
+            if (empty($auth)) {
+                $this->error('抱歉,您不在注册列表中,如需注册请联系妇联');
+            }
+
+            $user->realname     = $auth['name'];
+            $user->idcard       = $auth['idcard'];
+            $user->mobile       = $auth['mobile'];
+            $user->company      = $auth['company'];
+            $user->company      = $auth['company'];
+            $user->marry        = $auth['marry'];
+            $user->sex          = Fun::getSexByIdCard($auth['idcard']);
+            $user->birthday     = strtotime(Fun::getBirthDayByIdCard($auth['idcard']));
+            $user->check_status = 2;
+            $user->create_time  = time();
+            $user->save();
+
+            $this->success('操作成功');
+        } else {
+            if (!empty($user['idcard'])) {
+                $this->redirect(url('intro'));
+            }
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 活动说明
+     */
+    public function active()
+    {
+        $id                   = $this->request->param('id', 0);
+        $active               = ActiveModel::get($id);
+        $active['start_time'] = date('m-d', $active['start_time']);
+        $active['end_time']   = date('m-d', $active['end_time']);
+        $active['main_image'] = cmf_get_image_preview_url($active['main_image']);
+        $this->assign('active', $active);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 性别
+     */
+    public function sex()
+    {
+        $user = UserModel::get(cmf_get_current_user_id());
+        if ($this->request->isPost()) {
+            $value = $this->request->post('sex');
+            if (empty($user['sex'])) {
+                $user->sex = $value;
+                $user->save();
+            }
+            $this->success('');
+        } else {
+            if (!empty($user['sex'])) {
+                $this->redirect(url('intro'));
+            }
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 部门
+     */
+    public function department()
+    {
+        $user = UserModel::get(cmf_get_current_user_id());
+        if ($this->request->isPost()) {
+            $value = $this->request->post('department');
+            if (empty($user['department'])) {
+                $user->department = $value;
+                $user->save();
+            }
+            $this->success('');
+        } else {
+            $this->assign('department', Constant::DEPARTMENT);
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 头像
+     */
+    public function intro()
+    {
+        if ($this->request->isPost()) {
+            $value           = $this->request->post();
+            $value['avatar'] = $value['main_image_thumb'];
+            UserModel::update($value, ['id' => cmf_get_current_user_id()], ['avatar', 'main_image', 'main_image_thumb', 'signature']);
+            $this->success('');
+        } else {
+            $user = UserModel::get(cmf_get_current_user_id());
+            $this->assign('user', $user);
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 健康码
+     */
+    public function healthCode()
+    {
+        if ($this->request->isPost()) {
+            $value = $this->request->post();
+            UserModel::update($value, ['id' => cmf_get_current_user_id()], ['health_code']);
+            $this->success('');
+        } else {
+            $user = UserModel::get(cmf_get_current_user_id());
+            $this->assign('user', $user);
+            return $this->fetch();
+        }
+    }
+
+    /**
+     * 个人资料
+     */
+    public function profile()
+    {
+        if ($this->request->isPost()) {
+            $value                  = $this->request->post();
+            $value['user_nickname'] = $value['realname'];
+            $value['birthday']      = strtotime($value['birthday']);
+            UserModel::update($value, ['id' => cmf_get_current_user_id()]);
+            $this->success('');
+        } else {
+            $user             = UserModel::get(cmf_get_current_user_id());
+            $user['birthday'] = date('Y-m-d', $user['birthday']);
+            unset($user['user_pass']);
+            $this->assign('user', json_encode($user));
+
+            $data = [
+                'marry'     => Constant::MARRY,
+                'high'      => Constant::HIGH,
+                'weight'    => Constant::WEIGHT,
+                'education' => Constant::EDUCATION,
+                'income'    => Constant::INCOME,
+                'nation'    => Constant::NATION,
+                'smoke'     => Constant::SMOKE,
+                'drink'     => Constant::DRINK,
+                'family'    => Constant::FAMILY,
+                'tinyint'   => Constant::TINYINT,
+            ];
+            foreach ($data as &$v) {
+                $v = json_encode($v);
+                unset($v);
+            }
+
+            return $this->fetch('', $data);
+        }
+    }
+
+    /**
+     * 择偶要求
+     */
+    public function cond()
+    {
+        if ($this->request->isPost()) {
+            $param = $this->request->post();
+            UserMatingModel::update($param, ['user_id' => cmf_get_current_user_id()]);
+            UserModel::update(['is_complete' => 1, 'id' => cmf_get_current_user_id()]);
+            $user = UserModel::get(cmf_get_current_user_id());
+            session('user', $user->toArray());
+            $this->success('操作成功');
+        } else {
+            $mating = UserMatingModel::get(['user_id' => cmf_get_current_user_id()]);
+            $this->assign('mating', $mating);
+
+            $education   = Constant::EDUCATION;
+            $education[] = '无要求';
+            $data        = [
+                'min_age'    => Constant::MIN_AGE,
+                'max_age'    => Constant::MAX_AGE,
+                'min_high'   => Constant::MIN_HIGH,
+                'max_high'   => Constant::MAX_HIGH,
+                'min_weight' => Constant::MIN_WEIGHT,
+                'max_weight' => Constant::MAX_WEIGHT,
+                'native'     => Constant::NATIVE,
+                'education'  => $education,
+                'income'     => Constant::COND_INCOME,
+                'smoke'      => Constant::SMOKE,
+                'drink'      => Constant::DRINK,
+                'tinyint'    => Constant::COND_TINYINT,
+            ];
+            foreach ($data as &$v) {
+                $v = json_encode($v);
+                unset($v);
+            }
+
+            return $this->fetch('', $data);
+        }
+    }
+
+//    public function t1()
+//    {
+//        $auth = UserAuthModel::all();
+//        $num = 0;
+//        foreach ($auth as $v) {
+//            $birth = strtotime(Fun::getBirthDayByIdCard($v['idcard']));
+//            $check = UserModel::where('mobile',$v['mobile'])->find();
+//            if (!empty($check) && $check->birthday != $birth) {
+//                $check->birthday = $birth;
+//                $check->save();
+//                $num++;
+//            }
+//        }
+//
+//        return $num;
+//    }
+
+    /**
+     * 健康码上传
+     */
+    public function codeUpload()
+    {
+        $file = $this->request->post('file');
+        $ext  = pathinfo($this->request->post('name'))['extension'];
+        if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
+            $this->error('文件后缀必须为jpg,jpeg,png');
+        }
+
+        if ($file) {
+            //创建目录
+            $upload_dir = WEB_ROOT . 'upload/health_code';
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+            $upload_dir = $upload_dir . '/' . date('Ymd');
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+
+            //保存文件
+            $file_name = $this->_file_name($ext);
+            $file_path = $upload_dir . '/' . $file_name;
+            $is_upload = $this->_base64_image_content($file, $file_path);
+            if (!$is_upload) {
+                $this->error('上传失败,请重新上传');
+            }
+            $main_image = cmf_get_user_avatar_url('health_code' . '/' . date('Ymd') . '/' . $file_name);
+
+            $this->result([
+                'health_code' => cmf_get_user_avatar_url($main_image),
+            ], 1);
+        } else {
+            $this->error('请上传文件');
+        }
+    }
+
+    /**
+     * 图片上传
+     */
+    public function imageUpload()
+    {
+        $file = $this->request->post('file');
+        $ext  = pathinfo($this->request->post('name'))['extension'];
+        if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {
+            $this->error('文件后缀必须为jpg,jpeg,png');
+        }
+
+        if ($file) {
+            //创建目录
+            $upload_dir = WEB_ROOT . 'upload/image';
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+            $upload_dir = $upload_dir . '/' . date('Ymd');
+            if (!is_dir($upload_dir)) {
+                @mkdir($upload_dir);
+            }
+
+            //保存文件
+            $file_name = $this->_file_name($ext);
+            $file_path = $upload_dir . '/' . $file_name;
+            $is_upload = $this->_base64_image_content($file, $file_path);
+            if (!$is_upload) {
+                $this->error('上传失败,请重新上传');
+            }
+
+            //缩略图
+            $thumb_file = $this->_file_name($ext);
+            $image      = \think\Image::open($file_path);
+            $image->thumb(220, 270)->save($upload_dir . '/' . $thumb_file);
+
+            $main_image       = cmf_get_user_avatar_url('image' . '/' . date('Ymd') . '/' . $file_name);
+            $main_image_thumb = cmf_get_user_avatar_url('image' . '/' . date('Ymd') . '/' . $thumb_file);
+
+            $this->result([
+                'main_image'       => $main_image,
+                'main_image_thumb' => $main_image_thumb,
+            ], 1);
+        } else {
+            $this->error('请上传文件');
+        }
+    }
+
+    private function _file_name($ext)
+    {
+        //生成随机文件名
+        //定义一个包含大小写字母数字的字符串
+        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        //把字符串分割成数组
+        $newchars = str_split($chars);
+        //打乱数组
+        shuffle($newchars);
+        //从数组中随机取出15个字符
+        $chars_key = array_rand($newchars, 15);
+        //把取出的字符重新组成字符串
+        $fnstr = '';
+        for ($i = 0; $i < 15; $i++) {
+            $fnstr .= $newchars[$chars_key[$i]];
+        }
+        //输出文件名并做MD5加密
+        return md5($fnstr . microtime(true) * 1000) . '.' . $ext;
+    }
+
+    private function _base64_image_content($base64_image_content, $file_path)
+    {
+        //匹配出图片的格式
+        if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)) {
+            if (file_put_contents($file_path, base64_decode(str_replace($result[1], '', $base64_image_content)))) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+}

+ 266 - 0
app/love/controller/SceneController.php

@@ -0,0 +1,266 @@
+<?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\love\controller;
+
+use app\common\Fun;
+use app\common\Wechat;
+use app\love\model\ActiveApplyModel;
+use app\love\model\ActiveModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserModel;
+use app\love\model\UserSelectLogModel;
+use cmf\controller\HomeBaseController;
+use think\facade\Request;
+use think\facade\Session;
+
+class SceneController extends HomeBaseController
+{
+    public function initialize()
+    {
+        parent::initialize();
+
+        //分享页面
+        $wx_api_sign = Wechat::instance()->getJsSign(Request::url(true));
+        $this->assign('domain', $this->request->domain());
+        $this->assign('wx_api_sign', json_encode($wx_api_sign));
+    }
+
+    /**
+     * 签到
+     */
+    public function wechat()
+    {
+        $active_id = $this->request->param('active_id', 0);
+        if (empty($active_id)) {
+            $this->error('您的二维码有误,请联系工作人员', url('/'));
+        }
+        $active = ActiveModel::get($active_id);
+        if (empty($active)) {
+            $this->error('您的二维码有误,请联系工作人员', url('/'));
+        }
+        Session::set('sign_in_id', $active_id);
+
+        $this->redirect('https://www.jucai.gov.cn/api/auth/wechat_auth?url=' . urlencode(url('love/Scene/signIn', '', true, true)));
+    }
+
+    /**
+     * 签到回调
+     */
+    public function signIn()
+    {
+        $param     = $this->request->param();
+        $open_id   = $param['openid'];
+        $active_id = Session::pull('sign_in_id');
+
+        //获取用户
+        $user = UserModel::where('open_id', $open_id)->find();
+        if (empty($user)) {
+            $this->assign('content', '请先注册');
+            $this->assign('btn', '点击注册');
+            $this->assign('url', url('love/login/index') . '?id=' . $active_id);
+            return $this->fetch();
+        }
+
+        //用户状态
+        if ($user['is_complete'] == 2) {
+            $this->assign('content', '请先完善资料');
+            $this->assign('btn', '完善资料');
+            if (empty($user['sex'])) {
+                $this->assign('url', url('love/register/sex'));
+            } else {
+                $this->assign('url', url('love/register/intro'));
+            }
+            return $this->fetch();
+        }
+        if ($user['user_status'] != 1) {
+            $this->redirect(url('love/register/ban'));
+        }
+        if ($user['check_status'] != 2) {
+            $this->assign('content', '请等待后台审核');
+            $this->assign('btn', '前往首页');
+            $this->assign('url', url('/'));
+            return $this->fetch();
+        }
+
+        //活动
+        $apply = ActiveApplyModel::where([
+            ['active_id', '=', $active_id],
+            ['user_id', '=', $user['id']],
+        ])->find();
+        if (empty($apply)) {
+            $this->assign('content', '请先报名活动');
+            $this->assign('btn', '进入活动');
+            $this->assign('url', url('love/active/detail') . '?id=' . $active_id);
+            return $this->fetch();
+        }
+        if ($apply['check_status'] != 2) {
+            $this->assign('content', '活动还未审核通过');
+            $this->assign('btn', '进入活动');
+            $this->assign('url', url('love/active/detail') . '?id=' . $active_id);
+            return $this->fetch();
+        }
+
+        //签到
+        if ($apply->site_status == 2) {
+            $apply->site_status = 1;
+            $apply->site_time   = time();
+            $apply->save();
+        }
+
+        //嘉宾编号
+        if (empty($apply['user_no'])) {
+            $user_no        = ActiveApplyModel::where('active_id', $active_id)->max('user_no');
+            $apply->user_no = ++$user_no;
+            $apply->save();
+        }
+
+        $this->assign('content', '签到成功<br/>您的嘉宾编号是' . $apply['user_no']);
+        $this->assign('btn', '进入活动');
+        $this->assign('url', url('love/active/detail') . '?id=' . $active_id);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 现场轮播图
+     */
+    public function sceneList()
+    {
+        $id = $this->request->param('id', 0);
+        if (empty($id)) {
+            return '0';
+        }
+        $this->assign('id', $id);
+
+        $active = ActiveModel::get($id);
+        if (empty($active)) {
+            return '0';
+        }
+        $this->assign('active', $active);
+
+        //获取列表
+        $where = [
+            ['a.active_id', '=', $id],
+            ['a.site_status', '=', 1],
+        ];
+        $list  = $this->_getList($where);
+        $this->assign('list', $list);
+
+        //最后到场时间
+        $this->assign('last_time', $list->isEmpty() ? 0 : $list[0]['site_time']);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 现场轮播图信息
+     */
+    public function sceneListPost()
+    {
+        $param = $this->request->param();
+
+        //获取列表
+        $where = [
+            ['a.active_id', '=', $param['id']],
+            ['a.site_status', '=', 1],
+            ['a.site_time', '>', $param['time']],
+        ];
+        $list  = $this->_getList($where);
+
+        $this->success('', '', $list);
+    }
+
+    /**
+     * 获取列表
+     */
+    private function _getList($where)
+    {
+        $list = ActiveApplyModel::alias('a')
+            ->field('u.*,a.user_no,a.site_time')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where($where)->order('site_time desc')->select();
+        if ($list->isEmpty()) {
+            return $list;
+        }
+
+        //获取择偶要求
+        $age_list = $list->column('id');
+        $mating   = UserMatingModel::where('user_id', 'in', $age_list)->column('*', 'user_id');
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+            $v['mating'] = $mating[$v['id']];
+            $v['url']    = urlencode(url('love/userwall/detail', ['id' => $v['id']], true, true));
+        }
+
+        return $list;
+    }
+
+    /**
+     * 选择特效开始
+     */
+    public function selectStart()
+    {
+        return $this->fetch();
+    }
+
+    /**
+     * 选择特效
+     */
+    public function selectView()
+    {
+        $info = UserSelectLogModel::with('user1,user2')->where('is_top', 1)->find();
+        $this->assign('info', $info);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 帮助中心
+     */
+    public function helpCenter()
+    {
+        $this->assign('share_title', '速看 | 泉州人才联谊平台操作手册!');
+        $this->assign('share_desc', '点击查看>>>>');
+        $this->assign('share_link', $this->request->url(true));
+        return $this->fetch();
+    }
+
+    /**
+     * 随机抽人
+     */
+    public function randomPeople()
+    {
+        $id  = $this->request->param('id', 0);
+        $man = ActiveApplyModel::alias('a')
+            ->field('u.realname,u.main_image,u.username,u.star,a.user_no')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where([
+                ['a.active_id', '=', $id],
+                ['a.user_no', '>', 0],
+                ['u.sex', '=', 1],
+            ])->select();
+        $this->assign('man', json_encode($man));
+        $this->assign('man_count', $man->count() - 1);
+
+        $woman = ActiveApplyModel::alias('a')
+            ->field('u.realname,u.main_image,u.username,u.star,a.user_no')
+            ->leftJoin('cmf_user u', 'a.user_id = u.id')
+            ->where([
+                ['a.active_id', '=', $id],
+                ['a.user_no', '>', 0],
+                ['u.sex', '=', 2],
+            ])->select();
+        $this->assign('woman', json_encode($woman));
+        $this->assign('woman_count', $woman->count() - 1);
+
+        return $this->fetch();
+    }
+}

+ 484 - 0
app/love/controller/UserwallController.php

@@ -0,0 +1,484 @@
+<?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\love\controller;
+
+use app\common\Constant;
+use app\common\Fun;
+use app\love\model\GiftModel;
+use app\love\model\UserFavoriteModel;
+use app\love\model\UserFriendModel;
+use app\love\model\UserGiftModel;
+use app\love\model\UserInviteModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserModel;
+use app\love\model\UserSelectModel;
+use app\love\model\UserVisitModel;
+
+class UserwallController extends LoveBaseController
+{
+    private $_age = [[0, 100], [18, 25], [25, 30], [30, 40], [40, 50], [50, 100]];
+    private $_high = [[0, 250], [150, 160], [161, 170], [171, 180], [180, 250]];
+    private $_weight = [[0, 150], [40, 50], [51, 60], [61, 70], [71, 80], [81, 150]];
+
+    /**
+     * 缘份列表
+     */
+    public function index()
+    {
+        $list = UserModel::where('user_type', 2)
+            ->where('check_status', 2)
+            ->where('is_complete', 1)
+            ->where('is_public', 1)
+            ->where('id', '<>', cmf_get_current_user_id())
+            ->where('sex', '<>', cmf_get_current_user()['sex'])
+            ->limit(4)
+            ->select();
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->assign('list', $list);
+
+        //未读消息数
+        $unread_num = UserFriendModel::where('user_id', cmf_get_current_user_id())->sum('unread_num');
+        $invite_num = UserInviteModel::where('to_id', cmf_get_current_user_id())
+            ->where('status', 1)
+            ->count();
+        $this->assign('unread_num', $unread_num + $invite_num);
+
+        return $this->fetch("");
+    }
+
+    /**
+     * 列表
+     */
+    public function getList()
+    {
+        $param = $this->request->post();
+
+        $where = [
+            ['user_type', '=', 2],
+            ['id', '<>', cmf_get_current_user_id()],
+            ['check_status', '=', 2],
+            ['is_complete', '=', 1],
+            ['is_public', '=', 1],
+            ['sex', '<>', cmf_get_current_user()['sex']],
+        ];
+        if (!empty($param['age'])) {
+            $age        = $this->_age[$param['age']];
+            $start_time = strtotime("-{$age[1]} year");
+            $end_time   = strtotime("-{$age[0]} year");
+            $where[]    = ['birthday', 'between', [$start_time, $end_time]];
+        }
+//        if (!empty($param['sex'])) {
+//            $where[] = ['sex', '=', $param['sex']];
+//        }
+        if (!empty($param['id'])) {
+            $where[] = ['id|realname', 'like', "%{$param['id']}%"];
+        }
+        if (!empty($param['high'])) {
+            $high    = $this->_high[$param['high']];
+            $where[] = ['high', 'between', [$high[0], $high[1]]];
+        }
+        if (!empty($param['weight'])) {
+            $weight  = $this->_weight[$param['weight']];
+            $where[] = ['weight', 'between', [$weight[0], $weight[1]]];
+        }
+        if (!empty($param['education'])) {
+            $where[] = ['education', '=', $param['education']];
+        }
+
+        $list = UserModel::where($where)
+            ->page($param['page'])
+            ->limit(4)
+            ->order($param['sort'])
+            ->select();
+
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->result($list, 1);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        //获取会员信息
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        $this->assign('user_id', $user_id);
+        if (empty($id)) {
+            $this->error('该会员不存在!');
+        }
+        $user = UserModel::get(['id' => $id, 'user_type' => 2]);
+        if (empty($user)) {
+            $this->error('该会员不存在!');
+        }
+
+        //信息处理
+        $user->star++;
+        $user->save();
+        $user['age'] = Fun::getAgeByBirth($user['birthday']);
+        $user['have_house'] = Constant::COND_TINYINT[$user['have_house']];
+        $user['have_car'] = Constant::COND_TINYINT[$user['have_car']];
+        $user['with_parent_live'] = Constant::COND_TINYINT[$user['with_parent_live']];
+        $user['sex_text'] = $user['sex_text'];
+        if (empty($user['more'])) {
+            $user['more'] = [];
+        }
+        $this->assign('user', $user);
+
+        //择偶要求
+        $mating = UserMatingModel::get(['user_id' => $id]);
+        $mating['have_house'] = Constant::COND_TINYINT[$mating['have_house']];
+        $mating['have_car'] = Constant::COND_TINYINT[$mating['have_car']];
+        $mating['with_parent_live'] = Constant::COND_TINYINT[$mating['with_parent_live']];
+        $this->assign('mating', $mating);
+
+        //礼物列表
+        $gift = GiftModel::all();
+        $this->assign('gift', $gift);
+
+        //收到的礼物
+        $user_gift = UserGiftModel::with('gift')->where('to_id', $id)->limit(4)->order('create_time desc')->select();
+        $this->assign('user_gift', $user_gift);
+
+        //收藏
+        $where          = [
+            ['user_id', '=', $user_id],
+            ['uid', '=', $id],
+        ];
+        $favorite_check = UserFavoriteModel::where($where)->find();
+        $this->assign('is_favorite', $favorite_check ? 'true' : 'false');
+
+        //选择
+        $select_check = UserSelectModel::where($where)->find();
+        $this->assign('is_select', $select_check ? 'true' : 'false');
+
+        //增加访客记录
+        //足迹
+        $current_user = UserModel::get($user_id);
+        if ($user_id != $id && $current_user['show_footmark'] == 1) {
+            $visit = UserVisitModel::get(['from_id' => $user_id, 'to_id' => $id]);
+            $time  = time();
+            if (empty($visit)) {
+                UserVisitModel::create([
+                    'from_id'         => $user_id,
+                    'to_id'           => $id,
+                    'num'             => 1,
+                    'create_time'     => $time,
+                    'last_visit_time' => $time,
+                ]);
+            } else {
+                $visit->num++;
+                $visit->last_visit_time = $time;
+                $visit->save();
+            }
+        }
+
+        //是否好友
+        $is_friend = UserFriendModel::get([
+            'user_id'   => $id,
+            'friend_id' => $user_id,
+        ]);
+        $this->assign('is_friend', $is_friend ? 'true' : 'false');
+
+        $this->assign('share_image_url', $user['main_image_thumb']);
+        $this->assign('share_link', $this->request->url(true));
+        $this->assign('share_desc', $user['username'] . (empty($user['signature']) ? '' : (':' . $user['signature'])));
+        return $this->fetch();
+    }
+
+    /**
+     * 赠送礼物
+     */
+    public function giveGift()
+    {
+        $this->checkStatus();
+
+        $post    = $this->request->param();
+        $from_id = cmf_get_current_user_id();
+        if ($from_id == $post['to_id']) {
+            $this->error('不可以给自己送礼');
+        }
+
+        UserGiftModel::create([
+            'from_id'     => $from_id,
+            'to_id'       => $post['to_id'],
+            'gift_id'     => $post['gift_id'],
+            'create_time' => time(),
+        ]);
+        $this->success('赠送成功');
+    }
+
+    /**
+     * 收到的礼物
+     */
+    public function userGift()
+    {
+        $id        = $this->request->param('id');
+        $user_gift = UserGiftModel::with('gift')->where('to_id', $id)->limit(4)->order('create_time desc')->select();
+        $this->result($user_gift, 1);
+    }
+
+    /**
+     * 收藏
+     */
+    public function favorite()
+    {
+        $this->checkStatus();
+
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        if ($id == $user_id) {
+            $this->error('不可以收藏自己');
+        }
+        $where = [
+            ['user_id', '=', $user_id],
+            ['uid', '=', $id],
+        ];
+        $check = UserFavoriteModel::where($where)->find();
+        if ($check) {
+            UserFavoriteModel::where($where)->delete();
+        } else {
+            UserFavoriteModel::create([
+                'user_id'     => $user_id,
+                'uid'         => $id,
+                'create_time' => time(),
+            ]);
+        }
+        $this->success('操作成功');
+    }
+
+    /**
+     * 选择
+     */
+    public function select()
+    {
+        $this->checkStatus();
+
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        if ($id == $user_id) {
+            $this->error('不可以选择自己');
+        }
+
+        //已互选
+        $is_select = UserSelectModel::where('user_id|uid', '=', $user_id)->where('is_confirm', 1)->find();
+        if ($is_select) {
+            $this->error('您已有互选对象!');
+        }
+
+        //判断性别
+        $from_user = UserModel::get($user_id);
+        $to_user   = UserModel::get($id);
+        if ($from_user['sex'] == $to_user['sex']) {
+            $this->error('请选择异性!');
+        }
+
+        //此人已恋爱,请重新选择
+        $is_love = UserSelectModel::where('user_id|uid', '=', $id)->where('is_confirm', 1)->find();
+        if ($is_love) {
+            $this->error('此人正在交往中,请重新选择!');
+        }
+
+        //选择
+        $where = [
+            ['user_id', '=', $user_id],
+            ['uid', '=', $id],
+        ];
+        $check = UserSelectModel::where($where)->find();
+        if ($check) {
+            UserSelectModel::where($where)->delete();
+        } else {
+            UserSelectModel::create([
+                'user_id'     => $user_id,
+                'uid'         => $id,
+                'create_time' => time(),
+            ]);
+        }
+        $this->success('操作成功');
+    }
+
+    /**
+     * 申请聊天(旧)
+     */
+    /*public function chat()
+    {
+        $this->checkStatus();
+
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        $time    = time();
+
+        //聊天申请
+        $invite_get   = UserInviteModel::where([
+            ['from_id', '=', $id],
+            ['to_id', '=', $user_id],
+        ])->find();
+        $invite_apply = UserInviteModel::where([
+            ['from_id', '=', $user_id],
+            ['to_id', '=', $id],
+        ])->find();
+
+        //都没有邀请
+        if (empty($invite_get) && empty($invite_apply)) {
+            UserInviteModel::create([
+                'from_id'     => $user_id,
+                'to_id'       => $id,
+                'create_time' => $time,
+            ]);
+            UserModel::where('id', $id)->setInc('invite_num');
+            $this->error('请等待对方通过聊天邀请');
+        }
+
+        //已收到过邀请
+        if (!empty($invite_get)) {
+            if ($invite_get['status'] == 1) {
+                UserFriendModel::create([
+                    'user_id'       => $id,
+                    'friend_id'     => $user_id,
+                    'last_msg_time' => time(),
+                ]);
+                UserFriendModel::create([
+                    'user_id'       => $user_id,
+                    'friend_id'     => $id,
+                    'last_msg_time' => time(),
+                ]);
+                $invite_get->status = 2;
+                $invite_get->save();
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } elseif ($invite_get['status'] == 2) {
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } else {
+                $this->error('您已拒绝过对方的聊天邀请');
+            }
+        }
+
+        //已发起过邀请
+        if (!empty($invite_apply)) {
+            if ($invite_apply['status'] == 1) {
+                $this->error('请等待对方同意申请');
+            } elseif ($invite_apply['status'] == 2) {
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } else {
+                $this->error('对方拒绝跟您聊天');
+            }
+        }
+    }*/
+
+    /**
+     * 申请聊天
+     */
+    /*public function chat()
+    {
+        $this->checkStatus();
+
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        $time    = time();
+
+        //聊天申请
+        $invite_get   = UserInviteModel::where([
+            ['from_id', '=', $id],
+            ['to_id', '=', $user_id],
+        ])->find();
+        $invite_apply = UserInviteModel::where([
+            ['from_id', '=', $user_id],
+            ['to_id', '=', $id],
+        ])->find();
+
+        //都没有邀请
+        if (empty($invite_get) && empty($invite_apply)) {
+            UserInviteModel::create([
+                'from_id'     => $user_id,
+                'to_id'       => $id,
+                'create_time' => $time,
+            ]);
+            UserModel::where('id', $id)->setInc('invite_num');
+            $this->error('请等待对方通过聊天邀请');
+        }
+
+        //已收到过邀请
+        if (!empty($invite_get)) {
+            if ($invite_get['status'] == 1) {
+                UserFriendModel::create([
+                    'user_id'       => $id,
+                    'friend_id'     => $user_id,
+                    'last_msg_time' => time(),
+                ]);
+                UserFriendModel::create([
+                    'user_id'       => $user_id,
+                    'friend_id'     => $id,
+                    'last_msg_time' => time(),
+                ]);
+                $invite_get->status = 2;
+                $invite_get->save();
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } elseif ($invite_get['status'] == 2) {
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } else {
+                $this->error('您已拒绝过对方的聊天邀请');
+            }
+        }
+
+        //已发起过邀请
+        if (!empty($invite_apply)) {
+            if ($invite_apply['status'] == 1) {
+                $this->error('请等待对方同意申请');
+            } elseif ($invite_apply['status'] == 2) {
+                $this->success('', url('message/detail') . '?id=' . $id);
+            } else {
+                $this->error('对方拒绝跟您聊天');
+            }
+        }
+    }*/
+
+    /**
+     * 聊天
+     */
+    public function chat()
+    {
+        $this->checkStatus();
+
+        $id      = $this->request->param('id');
+        $user_id = cmf_get_current_user_id();
+        $time    = time();
+
+        $select = UserSelectModel::where('user_id|uid', '=', $user_id)
+            ->where('is_confirm', 1)
+            ->find();
+        if (!empty($select)) {
+            $this->error('您已有心上人,请一心一意对待哦(*^_^*)');
+        }
+
+        $has_freind = UserFriendModel::where([
+            'user_id'   => $user_id,
+            'friend_id' => $id,
+        ])->find();
+
+        //无好友先添加成好友
+        if (empty($has_freind)) {
+            UserFriendModel::create([
+                'user_id'       => $id,
+                'friend_id'     => $user_id,
+                'last_msg_time' => $time,
+            ]);
+            UserFriendModel::create([
+                'user_id'       => $user_id,
+                'friend_id'     => $id,
+                'last_msg_time' => $time,
+            ]);
+        }
+        $this->success('', url('message/detail') . '?id=' . $id);
+    }
+}

+ 28 - 0
app/love/model/ActiveApplyModel.php

@@ -0,0 +1,28 @@
+<?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\love\model;
+
+use think\Model;
+
+class ActiveApplyModel extends Model
+{
+    public function getStatusTextAttr($value, $data)
+    {
+        $status = ['', '待审核', '审核通过','审核不通过'];
+        return $status[$data['check_status']];
+    }
+
+    public function user()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id');
+    }
+
+}

+ 20 - 0
app/love/model/ActiveModel.php

@@ -0,0 +1,20 @@
+<?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\love\model;
+
+use think\Model;
+
+class ActiveModel extends Model
+{
+
+    protected $autoWriteTimestamp = false;
+
+}

+ 19 - 0
app/love/model/GiftModel.php

@@ -0,0 +1,19 @@
+<?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\love\model;
+
+use think\Model;
+
+class GiftModel extends Model
+{
+
+
+}

+ 27 - 0
app/love/model/LotteryLogModel.php

@@ -0,0 +1,27 @@
+<?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\love\model;
+
+use think\Model;
+
+class LotteryLogModel extends Model
+{
+
+    public function user()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id');
+    }
+
+    public function lottery()
+    {
+        return $this->hasOne(LotteryPrizeModel::class,'id','prize_id');
+    }
+}

+ 18 - 0
app/love/model/LotteryModel.php

@@ -0,0 +1,18 @@
+<?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\love\model;
+
+use think\Model;
+
+class LotteryModel extends Model
+{
+
+}

+ 18 - 0
app/love/model/LotteryPrizeModel.php

@@ -0,0 +1,18 @@
+<?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\love\model;
+
+use think\Model;
+
+class LotteryPrizeModel extends Model
+{
+
+}

+ 18 - 0
app/love/model/UserAuthModel.php

@@ -0,0 +1,18 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserAuthModel extends Model
+{
+
+}

+ 23 - 0
app/love/model/UserFavoriteModel.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\love\model;
+
+use think\Model;
+
+class UserFavoriteModel extends Model
+{
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','uid');
+    }
+
+}

+ 23 - 0
app/love/model/UserFriendModel.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\love\model;
+
+use think\Model;
+
+class UserFriendModel extends Model
+{
+
+    public function friend()
+    {
+        return $this->hasOne('UserModel','id','friend_id');
+    }
+
+}

+ 32 - 0
app/love/model/UserGiftModel.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\love\model;
+
+use think\Model;
+
+class UserGiftModel extends Model
+{
+
+    public function gift()
+    {
+        return $this->hasOne('GiftModel','id','gift_id');
+    }
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','to_id');
+    }
+
+    public function fromUser()
+    {
+        return $this->hasOne('UserModel','id','from_id');
+    }
+}

+ 27 - 0
app/love/model/UserInviteModel.php

@@ -0,0 +1,27 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserInviteModel extends Model
+{
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','to_id');
+    }
+
+    public function fromUser()
+    {
+        return $this->hasOne('UserModel','id','from_id');
+    }
+}

+ 27 - 0
app/love/model/UserMarryModel.php

@@ -0,0 +1,27 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserMarryModel extends Model
+{
+
+    public function user1()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id1');
+    }
+
+    public function user2()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id2');
+    }
+}

+ 18 - 0
app/love/model/UserMatingModel.php

@@ -0,0 +1,18 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserMatingModel extends Model
+{
+
+}

+ 28 - 0
app/love/model/UserMessageModel.php

@@ -0,0 +1,28 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserMessageModel extends Model
+{
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','to_id');
+    }
+
+    public function fromUser()
+    {
+        return $this->hasOne('UserModel','id','from_id');
+    }
+
+}

+ 28 - 0
app/love/model/UserModel.php

@@ -0,0 +1,28 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserModel extends Model
+{
+
+    protected $type = [
+        'family' => 'array',
+        'more'   => 'array',
+    ];
+
+    public function getSexTextAttr($value, $data)
+    {
+        $sex = ['保密', '男', '女'];
+        return $sex[$data['sex']];
+    }
+}

+ 32 - 0
app/love/model/UserSelectLogModel.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\love\model;
+
+use think\Model;
+
+class UserSelectLogModel extends Model
+{
+    // 定义全局的查询范围
+    protected function base($query)
+    {
+        $query->where('delete_time',null);
+    }
+
+    public function user1()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id1');
+    }
+
+    public function user2()
+    {
+        return $this->hasOne(UserModel::class,'id','user_id2');
+    }
+}

+ 28 - 0
app/love/model/UserSelectModel.php

@@ -0,0 +1,28 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserSelectModel extends Model
+{
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','user_id');
+    }
+
+    public function fromUser()
+    {
+        return $this->hasOne('UserModel','id','uid');
+    }
+
+}

+ 27 - 0
app/love/model/UserVisitModel.php

@@ -0,0 +1,27 @@
+<?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\love\model;
+
+use think\Model;
+
+class UserVisitModel extends Model
+{
+
+    public function toUser()
+    {
+        return $this->hasOne('UserModel','id','to_id');
+    }
+
+    public function fromUser()
+    {
+        return $this->hasOne('UserModel','id','from_id');
+    }
+}

+ 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'] = 0;
+            $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"));
+    }
+}

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

@@ -0,0 +1,102 @@
+<?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()
+    {
+
+        $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, '文章不存在!');
+        }
+
+
+        $prevArticle = $postService->publishedPrevArticle($articleId, $categoryId);
+        $nextArticle = $postService->publishedNextArticle($articleId, $categoryId);
+
+        $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);
+        $this->assign('prev_article', $prevArticle);
+        $this->assign('next_article', $nextArticle);
+
+        $tplName = empty($article['more']['template']) ? $tplName : $article['more']['template'];
+
+        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("您已赞过啦!");
+        }
+    }
+
+}

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

@@ -0,0 +1,104 @@
+<?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\common\Excel;
+use app\common\Fun;
+use app\love\controller\LoveBaseController;
+use app\love\model\UserFriendModel;
+use app\love\model\UserInviteModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserSelectLogModel;
+use app\portal\model\UserModel;
+
+class IndexController extends LoveBaseController
+{
+    public function index()
+    {
+        $user_id = cmf_get_current_user_id();
+        $user    = UserModel::get($user_id);
+        $matting = UserMatingModel::where('user_id', $user_id)->find();
+        $where   = [
+            ['user_type', '=', 2],
+            ['check_status', '=', 2],
+            ['is_complete', '=', 1],
+            ['is_public', '=', 1],
+            ['id', '<>', $user_id],
+            ['sex', '=', $user['sex'] == 1 ? 2 : 1],
+        ];
+        //年龄
+        $age_start = 0;
+        $age_end   = time();
+        if (!empty($matting['max_age'])) {
+            $age_start = strtotime("-{$matting['max_age']} year");
+        }
+        if (!empty($matting['min_age'])) {
+            $age_end = strtotime("-{$matting['min_age']} year");
+        }
+        $where[] = ['birthday', 'between', [$age_start, $age_end]];
+        //身高
+        $max_high = 200;
+        if (!empty($matting['max_high'])) {
+            $max_high = $matting['max_high'];
+        }
+        $where[] = ['high', 'between', [$matting['min_high'], $max_high]];
+        //体重
+        $max_weight = 200;
+        if (!empty($matting['max_weight'])) {
+            $max_weight = $matting['max_weight'];
+        }
+        $where[] = ['weight', 'between', [$matting['min_weight'], $max_weight]];
+
+        //会员
+        $list = UserModel::where($where)
+            ->order('star desc')
+            ->limit(6)
+            ->select();
+        foreach ($list as $v) {
+            $v['age'] = Fun::getAgeByBirth($v['birthday']);
+        }
+        $this->assign('list', json_encode($list));
+
+        //幻灯片
+        $images = SlideItemModel::where('slide_id', 1)->select();
+        foreach ($images as $image) {
+            $image['image'] = cmf_get_image_url($image['image']);
+        }
+        $this->assign('images', json_encode($images));
+
+        //未读消息数
+        $unread_num = UserFriendModel::where('user_id', cmf_get_current_user_id())->sum('unread_num');
+        $invite_num = UserInviteModel::where('to_id', cmf_get_current_user_id())
+            ->where('status', 1)
+            ->count();
+        $this->assign('unread_num', $unread_num + $invite_num);
+
+        //配对播报
+        /*$select = UserSelectLogModel::with(['user1','user2'])->order('id desc')->limit(3)->select();
+        if (!empty($select)) {
+            foreach ($select as $v) {
+                $v['name1'] = Fun::getEncodeName($v['user1']['realname']);
+                $v['name2'] = Fun::getEncodeName($v['user2']['realname']);
+            }
+        }
+        $this->assign('select',json_encode($select));
+        $select_count = UserSelectLogModel::useGlobalScope(false)->count();
+        $this->assign('select_count', $select_count + 4);*/
+
+        return $this->fetch(':index');
+    }
+
+    public function demo()
+    {
+        return $this->fetch(':demo');
+    }
+}

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

@@ -0,0 +1,39 @@
+<?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;
+
+class ListController 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', 0, 'intval');
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $category = $portalCategoryModel->where('id', $id)->where('status', 1)->find();
+       
+        $this->assign('category', $category);
+
+        $listTpl = empty($category['list_tpl']) ? 'list' : $category['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;
+    }
+
+
+}

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

@@ -0,0 +1,400 @@
+<?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;
+    }
+
+}

+ 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 "别名已经存在!";
+        }
+
+    }
+}

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

@@ -0,0 +1,211 @@
+<?php
+
+namespace app\test\controller;
+
+use app\common\Fun;
+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']      = Fun::getAgeByBirth($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);
+        }
+    }
+}

+ 332 - 0
app/user/controller/AdminIndexController.php

@@ -0,0 +1,332 @@
+<?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: Powerless < wzxaini9@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\user\controller;
+
+use api\user\model\UserLikeModel;
+use app\common\Constant;
+use app\common\Excel;
+use app\common\Fun;
+use app\love\model\ActiveApplyModel;
+use app\love\model\UserFriendModel;
+use app\love\model\UserGiftModel;
+use app\love\model\UserInviteModel;
+use app\love\model\UserMatingModel;
+use app\love\model\UserMessageModel;
+use app\love\model\UserSelectLogModel;
+use app\love\model\UserSelectModel;
+use app\love\model\UserVisitModel;
+use app\user\model\UserFavoriteModel;
+use app\user\model\UserModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+use think\db\Query;
+use \think\facade\Request;
+
+/**
+ * Class AdminIndexController
+ * @package app\user\controller
+ *
+ * @adminMenuRoot(
+ *     'name'   =>'用户管理',
+ *     'action' =>'default',
+ *     'parent' =>'',
+ *     'display'=> true,
+ *     'order'  => 10,
+ *     'icon'   =>'group',
+ *     'remark' =>'用户管理'
+ * )
+ *
+ * @adminMenuRoot(
+ *     'name'   =>'用户组',
+ *     'action' =>'default1',
+ *     'parent' =>'user/AdminIndex/default',
+ *     'display'=> true,
+ *     'order'  => 10000,
+ *     'icon'   =>'',
+ *     'remark' =>'用户组'
+ * )
+ */
+class AdminIndexController extends AdminBaseController
+{
+
+    /**
+     * 后台本站用户列表
+     * @adminMenu(
+     *     'name'   => '本站用户',
+     *     'parent' => 'default1',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '本站用户',
+     *     'param'  => ''
+     * )
+     */
+    public function index()
+    {
+        $content = hook_one('user_admin_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $count = Db::name('user')
+            ->where(function (Query $query) {
+                $data = $this->request->param();
+                $query->where('user_type', 2);
+                $query->where('is_complete', 1);
+                if (!empty($data['uid'])) {
+                    $query->where('id', intval($data['uid']));
+                }
+
+                if (!empty($data['keyword'])) {
+                    $keyword = $data['keyword'];
+                    $query->where('realname', 'like', "%$keyword%");
+                }
+
+                if (!empty($data['sex'])) {
+                    $query->where('sex', $data['sex']);
+                }
+
+                if (!empty($data['check_status'])) {
+                    $query->where('check_status', $data['check_status']);
+                }
+            })
+            ->count();
+        $this->assign('total',$count);
+
+        $list = Db::name('user')
+            ->where(function (Query $query) {
+                $data = $this->request->param();
+                $query->where('user_type', 2);
+                $query->where('is_complete', 1);
+                if (!empty($data['uid'])) {
+                    $query->where('id', intval($data['uid']));
+                }
+
+                if (!empty($data['keyword'])) {
+                    $keyword = $data['keyword'];
+                    $query->where('realname', 'like', "%$keyword%");
+                }
+
+                if (!empty($data['sex'])) {
+                    $query->where('sex', $data['sex']);
+                }
+
+                if (!empty($data['check_status'])) {
+                    $query->where('check_status', $data['check_status']);
+                }
+            })
+            ->order("check_status asc,create_time DESC")
+            ->paginate(10, false, [
+                'query' => Request::param(),//不丢失已存在的url参数
+            ]);
+
+        $this->assign('sex', $this->request->param('sex') ?: 0);
+        $this->assign('is_complete', $this->request->param('is_complete') ?: 0);
+        $this->assign('check_status', $this->request->param('check_status') ?: 0);
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+        // 渲染模板输出
+        return $this->fetch();
+    }
+
+    /**
+     * 审核
+     */
+    public function checkPost()
+    {
+        $data = $this->request->post();
+        UserModel::update($data, ['id' => $data['id']]);
+
+        $this->success('操作成功');
+    }
+
+    /**
+     * 详情
+     */
+    public function show()
+    {
+        //获取id
+        $id = input('param.id');
+        if (empty($id)) {
+            $this->error(lang('信息不存在或已删除'));
+        }
+
+        //获取信息
+        $info           = UserModel::get($id);
+        $info['family'] = $info['family'] ? implode(',', json_decode($info['family'], true)) : '未填写';
+        $info['have_house'] = Constant::COND_TINYINT[$info['have_house']];
+        $info['have_car'] = Constant::COND_TINYINT[$info['have_car']];
+        $info['with_parent_live'] = Constant::COND_TINYINT[$info['with_parent_live']];
+        if (empty($info)) {
+            $this->error(lang('信息不存在或已删除'));
+        }
+        $this->assign('info', $info);
+
+        $mating = UserMatingModel::get(['user_id' => $id]);
+        $mating['have_house'] = Constant::COND_TINYINT[$mating['have_house']];
+        $mating['have_car'] = Constant::COND_TINYINT[$mating['have_car']];
+        $mating['with_parent_live'] = Constant::COND_TINYINT[$mating['with_parent_live']];
+        $this->assign('mating', $mating);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 本站用户拉黑
+     * @adminMenu(
+     *     'name'   => '本站用户拉黑',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '本站用户拉黑',
+     *     'param'  => ''
+     * )
+     */
+    public function ban()
+    {
+        $id = input('param.id', 0, 'intval');
+        if ($id) {
+            $result = Db::name("user")->where(["id" => $id, "user_type" => 2])->setField('user_status', 0);
+            if ($result) {
+                $this->success("会员拉黑成功!", "adminIndex/index");
+            } else {
+                $this->error('会员拉黑失败,会员不存在,或者是管理员!');
+            }
+        } else {
+            $this->error('数据传入失败!');
+        }
+    }
+
+    /**
+     * 本站用户启用
+     * @adminMenu(
+     *     'name'   => '本站用户启用',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '本站用户启用',
+     *     'param'  => ''
+     * )
+     */
+    public function cancelBan()
+    {
+        $id = input('param.id', 0, 'intval');
+        if ($id) {
+            Db::name("user")->where(["id" => $id, "user_type" => 2])->setField('user_status', 1);
+            $this->success("会员启用成功!", '');
+        } else {
+            $this->error('数据传入失败!');
+        }
+    }
+
+    /**
+     * 删除用户
+     */
+    public function delete()
+    {
+        $id = input('param.id', 0, 'intval');
+        if ($id > 1) {
+            Db::startTrans();
+            try {
+                UserModel::destroy($id);
+                ActiveApplyModel::where('user_id', $id)->delete();
+                UserFavoriteModel::where('user_id|uid', '=', $id)->delete();
+                UserFriendModel::where('user_id|friend_id', '=', $id)->delete();
+                UserGiftModel::where('from_id|to_id', '=', $id)->delete();
+                UserInviteModel::where('from_id|to_id', '=', $id)->delete();
+                UserLikeModel::where('user_id', '=', $id)->delete();
+                UserMatingModel::where('user_id', '=', $id)->delete();
+                UserMessageModel::where('from_id|to_id', '=', $id)->delete();
+                UserSelectModel::where('user_id|uid', '=', $id)->delete();
+                UserSelectLogModel::where('user_id1|user_id2', '=', $id)->delete();
+                Db::name('user_token')->where('user_id', '=', $id)->delete();
+                UserVisitModel::where('from_id|to_id', '=', $id)->delete();
+                Db::commit();
+            } catch (\Exception $e) {
+                //回滚事务
+                Db::rollback();
+                $this->error('会员删除失败,会员不存在,或者是管理员!');
+            }
+            $this->success("会员删除成功!", "adminIndex/index");
+        } else {
+            $this->error('数据传入失败!');
+        }
+    }
+
+    /**
+     * 导出
+     */
+    public function export()
+    {
+        $data = $this->request->param();
+
+        $where = [
+            ['user_type', '=', 2],
+            ['is_complete', '=', 1],
+        ];
+
+        if (!empty($data['uid'])) {
+            $where[] = ['id', '=', intval($data['uid'])];
+        }
+
+        if (!empty($data['keyword'])) {
+            $where[] = ['realname', 'like', "%{$data['keyword']}%"];
+        }
+
+        if (!empty($data['sex'])) {
+            $where[] = ['sex', '=', $data['sex']];
+        }
+
+        if (!empty($data['check_status'])) {
+            $where[] = ['check_status', '=', $data['check_status']];
+        }
+
+        $list  = UserModel::where($where)->order("check_status asc,create_time DESC")->select();
+        $data = [];
+        foreach ($list as $v) {
+            $data[] = [
+                'name' => $v['realname'],
+                'sex' => $v['sex_text'],
+                'age' => Fun::getAgeByBirth($v['birthday']),
+                'mobile' => $v['mobile'],
+                'company' => $v['company'],
+                'native' => $v['native'],
+                'marry' => $v['marry'],
+            ];
+        }
+
+        if (empty($data)) {
+            return '暂无数据';
+        }
+
+        $excel = new Excel();
+        $title = [
+            ['name','姓名'],
+            ['sex','性别'],
+            ['age','年龄'],
+            ['mobile','电话'],
+            ['company','单位'],
+            ['native','籍贯'],
+            ['marry','婚姻状况'],
+        ];
+        $excel->export('本站用户',$title,$data,['mobile']);
+    }
+}

+ 213 - 0
app/user/controller/AdminUserController.php

@@ -0,0 +1,213 @@
+<?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: Powerless < wzxaini9@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace app\user\controller;
+
+use app\common\Constant;
+use app\common\Excel;
+use app\common\Fun;
+use app\love\model\UserAuthModel;
+use cmf\controller\AdminBaseController;
+use \think\facade\Request;
+use think\facade\Validate;
+
+class AdminUserController extends AdminBaseController
+{
+
+    /**
+     * 允许注册用户列表
+     */
+    public function auth()
+    {
+        $keyword = $this->request->param('keyword');
+        $list    = UserAuthModel::where(function ($query) use ($keyword) {
+            if (!empty($keyword)) {
+                $query->where('name|idcard|mobile', 'like', "%$keyword%");
+            }
+        })
+            ->order("id DESC")
+            ->paginate(10, false, [
+                'query' => Request::param(),//不丢失已存在的url参数
+            ]);
+
+        $count = UserAuthModel::where(function ($query) use ($keyword) {
+            if (!empty($keyword)) {
+                $query->where('name|idcard|mobile', 'like', "%$keyword%");
+            }
+        })
+            ->count();
+        $this->assign('total', $count);
+
+        $this->assign('keyword', $keyword ?: '');
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign('list', $list);
+        $this->assign('page', $page);
+        // 渲染模板输出
+        return $this->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function authAdd()
+    {
+        $this->assign('marry', Constant::MARRY);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加提交
+     */
+    public function authAddPost()
+    {
+        if ($this->request->isPost()) {
+            $post     = $this->request->param('post/a');
+            $validate = Validate::make([
+                'name'   => 'require',
+                'idcard' => 'require|idCard|unique:user_auth',
+                'mobile' => 'require|mobile|unique:user_auth',
+            ], [
+                'name'           => '请输入姓名',
+                'idcard.require' => '请输入身份证号',
+                'idcard.idcard'  => '身份证号不正确',
+                'idcard.unique'  => '身份证号已存在',
+                'mobile.require' => '请输入手机号',
+                'mobile.mobile'  => '手机号不正确',
+                'mobile.unique'  => '手机号已存在',
+            ]);
+            if (!$validate->check($post)) {
+                $this->error($validate->getError());
+            }
+
+            UserAuthModel::create($post);
+            $this->success('操作成功');
+        }
+    }
+
+    /**
+     * 编辑
+     */
+    public function authEdit()
+    {
+        $id = $this->request->param('id', 0);
+        if (empty($id)) {
+            $this->error('数据不存在');
+        }
+        $auth = UserAuthModel::get($id);
+        if (empty($auth)) {
+            $this->error('数据不存在');
+        }
+        $this->assign('auth', $auth);
+
+        $this->assign('marry', Constant::MARRY);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加提交
+     */
+    public function authEditPost()
+    {
+        if ($this->request->isPost()) {
+            $post     = $this->request->param('post/a');
+            $validate = Validate::make([
+                'name'   => 'require',
+                'idcard' => 'require|idCard|unique:user_auth,idcard,' . $post['id'],
+                'mobile' => 'require|mobile|unique:user_auth,mobile,' . $post['id'],
+            ], [
+                'name'           => '请输入姓名',
+                'idcard.require' => '请输入身份证号',
+                'idcard.idcard'  => '身份证号不正确',
+                'idcard.unique'  => '身份证号已存在',
+                'mobile.require' => '请输入手机号',
+                'mobile.mobile'  => '手机号不正确',
+                'mobile.unique'  => '手机号已存在',
+            ]);
+            if (!$validate->check($post)) {
+                $this->error($validate->getError());
+            }
+
+            UserAuthModel::update($post, ['id' => $post['id']]);
+            $this->success('操作成功');
+        }
+    }
+
+    /**
+     * 删除用户
+     */
+    public function authDelete()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+
+        if (UserAuthModel::destroy($id) !== false) {
+            $this->success("删除成功!");
+        } else {
+            $this->error("删除失败!");
+        }
+    }
+
+    /**
+     * 导入
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function import()
+    {
+        $url  = $this->request->post('url');
+        $file = WEB_ROOT . trim($url, '/');
+        if (file_exists($file)) {
+            $excel  = new Excel();
+            $data   = $excel->import($file, ['no', 'name', 'company', 'idcard', 'marry', 'mobile', 'marry'], 1);
+            $marry  = Constant::MARRY;
+            $res = [];
+            foreach ($data as $k => $v) {
+                if (empty($v['name'])) {
+                    $this->error("第" . ($k + 2) . "行的姓名不能为空");
+                }
+                if (empty($v['idcard'])) {
+                    $this->error("第" . ($k + 2) . "行的身份证号不能为空");
+                }
+                if (empty($v['mobile'])) {
+                    $this->error("第" . ($k + 2) . "行的手机号不能为空");
+                }
+                if (!Fun::isIdCard($v['idcard'])) {
+                    $this->error("第" . ($k + 2) . "行的身份证号格式错误");
+                }
+                if (!Fun::isMobile($v['mobile'])) {
+                    $this->error("第" . ($k + 2) . "行的手机号格式错误");
+                }
+                $idcardCheck = UserAuthModel::where('idcard', $v['idcard'])->find();
+                if (!empty($idcardCheck)) {
+                    $this->error("第" . ($k + 2) . "行的身份证号已存在");
+                }
+                $mobileCheck = UserAuthModel::where('mobile', $v['mobile'])->find();
+                if (!empty($mobileCheck)) {
+                    $this->error("第" . ($k + 2) . "行的手机号已存在");
+                }
+                if (!in_array($v['marry'], $marry)) {
+                    $this->error("第" . ($k + 2) . "行的婚姻状况错误");
+                }
+                $res[] = $v;
+            }
+
+            foreach ($res as $i) {
+                UserAuthModel::create($i);
+            }
+        }
+
+        $this->success('导入成功');
+    }
+
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません