diff --git a/application/admin/controller/Block.php b/application/admin/controller/Block.php
new file mode 100644
index 000000000..333622c8f
--- /dev/null
+++ b/application/admin/controller/Block.php
@@ -0,0 +1,114 @@
+
+ * @date 2017/03/27 14:43
+ */
+class Block extends BasicAdmin
+{
+
+ /**
+ * 定义当前默认数据表
+ * @var string
+ */
+ public $table = 'WechatFans';
+
+ /**
+ * 黑名单列表
+ * @return array|string
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\Exception
+ */
+ public function index()
+ {
+ $this->title = '微信黑名单管理';
+ $get = $this->request->get();
+ $db = Db::name($this->table)->where(['is_black' => '1']);
+ (isset($get['sex']) && $get['sex'] !== '') && $db->where('sex', $get['sex']);
+ foreach (['nickname', 'country', 'province', 'city'] as $key) {
+ (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%");
+ }
+ if (isset($get['tag']) && $get['tag'] !== '') {
+ $db->where("concat(',',tagid_list,',') like :tag", ['tag' => "%,{$get['tag']},%"]);
+ }
+ if (isset($get['create_at']) && $get['create_at'] !== '') {
+ list($start, $end) = explode(' - ', $get['create_at']);
+ $db->whereBetween('subscribe_at', ["{$start} 00:00:00", "{$end} 23:59:59"]);
+ }
+ return parent::_list($db->order('subscribe_time desc'));
+ }
+
+ /**
+ * 列表数据处理
+ * @param array $list
+ */
+ protected function _data_filter(&$list)
+ {
+ $tags = Db::name('WechatFansTags')->column('id,name');
+ foreach ($list as &$vo) {
+ list($vo['tags_list'], $vo['nickname']) = [[], ToolsService::emojiDecode($vo['nickname'])];
+ foreach (explode(',', $vo['tagid_list']) as $tag) {
+ if ($tag !== '' && isset($tags[$tag])) {
+ $vo['tags_list'][$tag] = $tags[$tag];
+ } elseif ($tag !== '') {
+ $vo['tags_list'][$tag] = $tag;
+ }
+ }
+ }
+ $this->assign('tags', $tags);
+ }
+
+ /**
+ * 取消黑名
+ */
+ public function backdel()
+ {
+ $openids = $this->_getActionOpenids();
+ try {
+ WechatService::user()->batchUnblackList($openids);
+ Db::name($this->table)->whereIn('openid', $openids)->setField('is_black', '0');
+ } catch (\Exception $e) {
+ $this->error("设备黑名单失败,请稍候再试!" . $e->getMessage());
+ }
+ $this->success("已成功将 " . count($openids) . " 名粉丝从黑名单中移除!", '');
+ }
+
+ /**
+ * 获取当前操作用户openid数组
+ * @return array
+ */
+ private function _getActionOpenids()
+ {
+ $ids = $this->request->post('id', '');
+ empty($ids) && $this->error('没有需要操作的数据!');
+ $openids = Db::name($this->table)->whereIn('id', explode(',', $ids))->column('openid');
+ empty($openids) && $this->error('没有需要操作的数据!');
+ return $openids;
+ }
+
+}
diff --git a/application/admin/controller/Fans.php b/application/admin/controller/Fans.php
new file mode 100644
index 000000000..6b96aeedb
--- /dev/null
+++ b/application/admin/controller/Fans.php
@@ -0,0 +1,191 @@
+
+ * @date 2017/03/27 14:43
+ */
+class Fans extends BasicAdmin
+{
+
+ /**
+ * 定义当前默认数据表
+ * @var string
+ */
+ public $table = 'WechatFans';
+
+ /**
+ * 显示粉丝列表
+ * @return array|string
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\Exception
+ */
+ public function index()
+ {
+ $this->title = '微信粉丝管理';
+ $get = $this->request->get();
+ $db = Db::name($this->table)->where(['is_black' => '0']);
+ (isset($get['sex']) && $get['sex'] !== '') && $db->where('sex', $get['sex']);
+ foreach (['nickname', 'country', 'province', 'city'] as $key) {
+ (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%");
+ }
+ if (isset($get['tag']) && $get['tag'] !== '') {
+ $db->where("concat(',',tagid_list,',') like :tag", ['tag' => "%,{$get['tag']},%"]);
+ }
+ if (isset($get['create_at']) && $get['create_at'] !== '') {
+ list($start, $end) = explode(' - ', $get['create_at']);
+ $db->whereBetween('subscribe_at', ["{$start} 00:00:00", "{$end} 23:59:59"]);
+ }
+ return parent::_list($db->order('subscribe_time desc'));
+ }
+
+ /**
+ * 列表数据处理
+ * @param array $list
+ */
+ protected function _data_filter(&$list)
+ {
+ $tags = Db::name('WechatFansTags')->column('id,name');
+ foreach ($list as &$vo) {
+ list($vo['tags_list'], $vo['nickname']) = [[], ToolsService::emojiDecode($vo['nickname'])];
+ foreach (explode(',', $vo['tagid_list']) as $tag) {
+ if ($tag !== '' && isset($tags[$tag])) {
+ $vo['tags_list'][$tag] = $tags[$tag];
+ } elseif ($tag !== '') {
+ $vo['tags_list'][$tag] = $tag;
+ }
+ }
+ }
+ $this->assign('tags', $tags);
+ }
+
+ /**
+ * 设置黑名单
+ */
+ public function backadd()
+ {
+ try {
+ $openids = $this->_getActionOpenids();
+ WechatService::user()->batchBlackList($openids);
+ Db::name($this->table)->whereIn('openid', $openids)->setField('is_black', '1');
+ } catch (\Exception $e) {
+ $this->error("设置黑名单失败,请稍候再试!");
+ }
+ $this->success('设置黑名单成功!', '');
+ }
+
+ /**
+ * 标签选择
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function tagset()
+ {
+ $tags = $this->request->post('tags', '');
+ $fans_id = $this->request->post('fans_id', '');
+ $fans = Db::name('WechatFans')->where(['id' => $fans_id])->find();
+ empty($fans) && $this->error('需要操作的数据不存在!');
+ try {
+ $wechat = WechatService::tags();
+ foreach (explode(',', $fans['tagid_list']) as $tagid) {
+ is_numeric($tagid) && $wechat->batchUntagging([$fans['openid']], $tagid);
+ }
+ foreach (explode(',', $tags) as $tagid) {
+ is_numeric($tagid) && $wechat->batchTagging([$fans['openid']], $tagid);
+ }
+ Db::name('WechatFans')->where(['id' => $fans_id])->setField('tagid_list', $tags);
+ } catch (\Exception $e) {
+ $this->error('粉丝标签设置失败, 请稍候再试!');
+ }
+ $this->success('粉丝标签成功!', '');
+ }
+
+ /**
+ * 给粉丝增加标签
+ */
+ public function tagadd()
+ {
+ $tagid = $this->request->post('tag_id', 0);
+ empty($tagid) && $this->error('没有可能操作的标签ID');
+ try {
+ $openids = $this->_getActionOpenids();
+ WechatService::tags()->batchTagging($openids, $tagid);
+ } catch (\Exception $e) {
+ $this->error("设置粉丝标签失败, 请稍候再试! " . $e->getMessage());
+ }
+ $this->success('设置粉丝标签成功!', '');
+ }
+
+ /**
+ * 移除粉丝标签
+ */
+ public function tagdel()
+ {
+ $tagid = $this->request->post('tag_id', 0);
+ empty($tagid) && $this->error('没有可能操作的标签ID');
+ try {
+ $openids = $this->_getActionOpenids();
+ WechatService::tags()->batchUntagging($openids, $tagid);
+ } catch (\Exception $e) {
+ $this->error("删除粉丝标签失败, 请稍候再试! ");
+ }
+ $this->success('删除粉丝标签成功!', '');
+ }
+
+ /**
+ * 获取当前操作用户openid数组
+ * @return array
+ */
+ private function _getActionOpenids()
+ {
+ $ids = $this->request->post('id', '');
+ empty($ids) && $this->error('没有需要操作的数据!');
+ $openids = Db::name($this->table)->whereIn('id', explode(',', $ids))->column('openid');
+ empty($openids) && $this->error('没有需要操作的数据!');
+ return $openids;
+ }
+
+ /**
+ * 同步粉丝列表
+ */
+ public function sync()
+ {
+ try {
+ Db::name($this->table)->where('1=1')->delete();
+ FansService::sync();
+ TagsService::sync();
+ LogService::write('微信管理', '同步全部微信粉丝成功');
+ } catch (\Exception $e) {
+ $this->error('同步粉丝记录失败,请稍候再试!');
+ }
+ $this->success('同步获取所有粉丝成功!', '');
+ }
+
+}
diff --git a/application/admin/controller/Keys.php b/application/admin/controller/Keys.php
new file mode 100644
index 000000000..6c7302dfe
--- /dev/null
+++ b/application/admin/controller/Keys.php
@@ -0,0 +1,202 @@
+
+ * @date 2017/03/27 14:43
+ */
+class Keys extends BasicAdmin
+{
+
+ /**
+ * 指定当前数据表
+ * @var string
+ */
+ public $table = 'WechatKeys';
+
+ /**
+ * 显示关键字列表
+ * @return array|string
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\Exception
+ */
+ public function index()
+ {
+ $this->assign('title', '微信关键字');
+ $db = Db::name($this->table)->whereNotIn('keys', ['subscribe', 'default']);
+ return $this->_list($db->order('id desc'));
+ }
+
+ /**
+ * 列表数据处理
+ * @param array $data
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ */
+ protected function _index_data_filter(&$data)
+ {
+ $types = [
+ 'keys' => '关键字', 'image' => '图片', 'news' => '图文',
+ 'music' => '音乐', 'text' => '文字', 'video' => '视频', 'voice' => '语音',
+ ];
+ $wechat = WechatService::qrcode();
+ foreach ($data as &$vo) {
+ $result = $wechat->create($vo['keys']);
+ $vo['qrc'] = $wechat->url($result['ticket']);
+ $vo['type'] = isset($types[$vo['type']]) ? $types[$vo['type']] : $vo['type'];
+ }
+ }
+
+ /**
+ * 添加关键字
+ * @return string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function add()
+ {
+ $this->title = '添加关键字规则';
+ return $this->_form($this->table, 'form');
+ }
+
+ /**
+ * 编辑关键字
+ * @return string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function edit()
+ {
+ $this->title = '编辑关键字规则';
+ return $this->_form($this->table, 'form');
+ }
+
+
+ /**
+ * 删除关键字
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function del()
+ {
+ if (DataService::update($this->table)) {
+ $this->success("关键字删除成功!", '');
+ }
+ $this->error("关键字删除失败,请稍候再试!");
+ }
+
+ /**
+ * 关键字禁用
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function forbid()
+ {
+ if (DataService::update($this->table)) {
+ $this->success("关键字禁用成功!", '');
+ }
+ $this->error("关键字禁用失败,请稍候再试!");
+ }
+
+ /**
+ * 关键字禁用
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function resume()
+ {
+ if (DataService::update($this->table)) {
+ $this->success("关键字启用成功!", '');
+ }
+ $this->error("关键字启用失败,请稍候再试!");
+ }
+
+ /**
+ * 关注默认回复
+ * @return array|string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function subscribe()
+ {
+ $this->assign('title', '编辑默认回复');
+ $extend = ['keys' => 'subscribe'];
+ return $this->_form($this->table, 'form', 'keys', $extend, $extend);
+ }
+
+
+ /**
+ * 无配置默认回复
+ * @return array|string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function defaults()
+ {
+ $this->assign('title', '编辑无配置默认回复');
+ $extend = ['keys' => 'default'];
+ return $this->_form($this->table, 'form', 'keys', $extend, $extend);
+ }
+
+ /**
+ * 添加数据处理
+ * @param array $data
+ */
+ protected function _form_filter(array &$data)
+ {
+ if ($this->request->isPost() && isset($data['keys'])) {
+ $db = Db::name($this->table)->where('keys', $data['keys']);
+ !empty($data['id']) && $db->where('id', 'neq', $data['id']);
+ $data['content'] = htmlspecialchars_decode($data['content']);
+ $db->count() > 0 && $this->error('关键字已经存在,请使用其它关键字!');
+ }
+ }
+
+ /**
+ * 编辑结果处理
+ * @param $result
+ */
+ protected function _form_result($result)
+ {
+ if ($result !== false) {
+ list($url, $keys) = ['', $this->request->post('keys')];
+ if (!in_array($keys, ['subscribe', 'default'])) {
+ $url = url('@admin') . '#' . url('wechat/keys/index') . '?spm=' . $this->request->get('spm');
+ }
+ $this->success('恭喜, 关键字保存成功!', $url);
+ }
+ $this->error('关键字保存失败, 请稍候再试!');
+ }
+
+}
diff --git a/application/admin/controller/News.php b/application/admin/controller/News.php
new file mode 100644
index 000000000..851d01853
--- /dev/null
+++ b/application/admin/controller/News.php
@@ -0,0 +1,297 @@
+
+ * @date 2017/03/27 14:43
+ */
+class News extends BasicAdmin
+{
+
+ /**
+ * 设置默认操作表
+ * @var string
+ */
+ public $table = 'WechatNews';
+
+ /**
+ * 图文列表
+ * @return array|string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function index()
+ {
+ $this->title = '微信图文列表';
+ $db = Db::name($this->table)->where(['is_deleted' => '0']);
+ return parent::_list($db->order('id desc'));
+ }
+
+ /**
+ * 图文列表数据处理
+ * @param array $data
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ protected function _index_data_filter(&$data)
+ {
+ foreach ($data as &$vo) {
+ $vo = MediaService::getNewsById($vo['id']);
+ }
+ }
+
+ /**
+ * 图文选择器
+ * @return string
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\Exception
+ */
+ public function select()
+ {
+ return $this->index();
+ }
+
+ /**
+ * 图文列表数据处理
+ * @param array $data
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ protected function _select_data_filter(&$data)
+ {
+ foreach ($data as &$vo) {
+ $vo = MediaService::getNewsById($vo['id']);
+ }
+ }
+
+ /**
+ * 媒体资源显示
+ * @return array
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\Exception
+ */
+ public function image()
+ {
+ $_GET['rows'] = 18;
+ $this->assign('field', $this->request->get('field', 'local_url'));
+ return $this->_list(Db::name('WechatNewsMedia')->where('type', 'image'));
+ }
+
+ /**
+ * 添加图文
+ * @return string
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function add()
+ {
+ if ($this->request->isGet()) {
+ return $this->fetch('form', ['title' => '新建图文']);
+ }
+ if ($this->request->isPost()) {
+ $data = $this->request->post();
+ if (($ids = $this->_apply_news_article($data['data'])) && !empty($ids)) {
+ $post = ['article_id' => $ids, 'create_by' => session('user.id')];
+ if (DataService::save($this->table, $post, 'id') !== false) {
+ $url = url('@admin') . '#' . url('@wechat/news/index') . '?spm=' . $this->request->get('spm');
+ $this->success('图文添加成功!', $url);
+ }
+ }
+ $this->error('图文添加失败,请稍候再试!');
+ }
+ }
+
+ /**
+ * 编辑图文
+ * @return string
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function edit()
+ {
+ $id = $this->request->get('id', '');
+ if ($this->request->isGet()) {
+ empty($id) && $this->error('参数错误,请稍候再试!');
+ return $this->fetch('form', ['title' => '编辑图文', 'vo' => MediaService::getNewsById($id)]);
+ }
+ $data = $this->request->post();
+ $ids = $this->_apply_news_article($data['data']);
+ if (!empty($ids)) {
+ $post = ['id' => $id, 'article_id' => $ids, 'create_by' => session('user.id')];
+ if (false !== DataService::save('wechat_news', $post, 'id')) {
+ $url = url('@admin') . '#' . url('@wechat/news/index') . '?spm=' . $this->request->get('spm');
+ $this->success('图文更新成功!', $url);
+ }
+ }
+ $this->error('图文更新失败,请稍候再试!');
+ }
+
+ /**
+ * 图文更新操作
+ * @param array $data
+ * @param array $ids
+ * @return string
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ protected function _apply_news_article($data, $ids = [])
+ {
+ foreach ($data as &$vo) {
+ $vo['create_by'] = session('user.id');
+ $vo['create_at'] = date('Y-m-d H:i:s');
+ if (empty($vo['digest'])) {
+ $vo['digest'] = mb_substr(strip_tags(str_replace(["\s", ' '], '', htmlspecialchars_decode($vo['content']))), 0, 120);
+ }
+ if (empty($vo['id'])) {
+ $result = $id = Db::name('WechatNewsArticle')->insertGetId($vo);
+ } else {
+ $id = intval($vo['id']);
+ $result = Db::name('WechatNewsArticle')->where('id', $id)->update($vo);
+ }
+ if ($result !== false) {
+ $ids[] = $id;
+ }
+ }
+ return join(',', $ids);
+ }
+
+ /**
+ * 删除用户
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function del()
+ {
+ if (DataService::update($this->table)) {
+ $this->success("图文删除成功!", '');
+ }
+ $this->error("图文删除失败, 请稍候再试!");
+ }
+
+ /**
+ * 推荐图文
+ * @return array
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\exception\PDOException
+ */
+ public function push()
+ {
+ # 获取将要推送的粉丝列表
+ switch (strtolower($this->request->get('action', ''))) {
+ case 'getuser':
+ if ('' === ($params = $this->request->post('group', ''))) {
+ return ['code' => 'SUCCESS', 'data' => []];
+ }
+ list($ids, $db) = [explode(',', $params), Db::name('WechatFans')];
+ !in_array('0', $ids) && $db->where("concat(',',tagid_list,',') REGEXP '," . join(',|,', $ids) . ",'");
+ $list = $db->where(['subscribe' => '1'])->limit(200)->column('nickname');
+ foreach ($list as &$vo) {
+ $vo = ToolsService::emojiDecode($vo);
+ }
+ return ['code' => "SUCCESS", 'data' => $list];
+ default :
+ $news_id = $this->request->get('id', '');
+ // 显示及图文
+ $newsinfo = MediaService::getNewsById($news_id);
+ // Get 请求,显示选择器界面
+ if ($this->request->isGet()) {
+ $fans_tags = (array)Db::name('WechatFansTags')->select();
+ $count = Db::name('WechatFans')->where(['subscribe' => '1'])->count();
+ array_unshift($fans_tags, ['id' => 0, 'name' => '全部', 'count' => $count]);
+ return $this->fetch('push', ['vo' => $newsinfo, 'fans_tags' => $fans_tags]);
+ }
+ // Post 请求,执行图文推送操作
+ $post = $this->request->post();
+ empty($post['fans_tags']) && $this->error('还没有选择要粉丝对象!');
+ // 图文上传操作
+ !$this->_uploadWechatNews($newsinfo) && $this->error('图文上传失败,请稍候再试!');
+ // 数据拼装
+ $data = [];
+ if (in_array('0', $post['fans_tags'])) {
+ $data['msgtype'] = 'mpnews';
+ $data['filter'] = ['is_to_all' => true];
+ $data['mpnews'] = ['media_id' => $newsinfo['media_id']];
+ } else {
+ $data['msgtype'] = 'mpnews';
+ $data['filter'] = ['is_to_all' => false, 'tag_id' => join(',', $post['fans_tags'])];
+ $data['mpnews'] = ['media_id' => $newsinfo['media_id']];
+ }
+ $wechat = WechatService::custom();
+ if (false !== $wechat->massSendAll($data)) {
+ LogService::write('微信管理', "图文[{$news_id}]推送成功");
+ $this->success('微信图文推送成功!', '');
+ }
+ $this->error("微信图文推送失败");
+ }
+ }
+
+ /**
+ * 上传永久图文
+ * @param array $news
+ * @return bool
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ private function _uploadWechatNews(&$news)
+ {
+ foreach ($news['articles'] as &$article) {
+ $article['thumb_media_id'] = MediaService::uploadForeverMedia($article['local_url']);
+ $article['content'] = preg_replace_callback("//i", function ($matches) {
+ $src = MediaService::uploadImage($matches[2]);
+ return "
";
+ }, htmlspecialchars_decode($article['content']));
+ }
+ $wechat = WechatService::media();
+ // 如果已经上传过,先删除之前的历史记录
+ !empty($news['media_id']) && $wechat->delMaterial($news['media_id']);
+ // 上传图文到微信服务器
+ $result = $wechat->addNews(['articles' => $news['articles']]);
+ if (isset($result['media_id'])) {
+ $news['media_id'] = $result['media_id'];
+ return Db::name('WechatNews')->where(['id' => $news['id']])->update(['media_id' => $result['media_id']]);
+ }
+ Log::error("上传永久图文失败");
+ return false;
+ }
+
+}
diff --git a/application/admin/controller/Review.php b/application/admin/controller/Review.php
new file mode 100644
index 000000000..62ffdf5b3
--- /dev/null
+++ b/application/admin/controller/Review.php
@@ -0,0 +1,61 @@
+", $this->request->get('content', '', 'urldecode')); // 内容
+ $type = $this->request->get('type', 'text'); // 类型
+ // 图文处理
+ if ($type === 'news' && is_numeric($content) && !empty($content)) {
+ $news = MediaService::getNewsById($content);
+ $this->assign('articles', $news['articles']);
+ }
+ // 文章预览
+ if ($type === 'article' && is_numeric($content) && !empty($content)) {
+ $article = Db::name('WechatNewsArticle')->where('id', $content)->find();
+ if (!empty($article['content_source_url'])) {
+ $this->redirect($article['content_source_url']);
+ }
+ $article['content'] = htmlspecialchars_decode($article['content']);
+ $this->assign('vo', $article);
+ }
+ $this->assign('type', $type);
+ $this->assign('content', $content);
+ $this->assign($this->request->get());
+ // 渲染模板并显示
+ return $this->fetch();
+ }
+
+}
diff --git a/application/admin/controller/Tags.php b/application/admin/controller/Tags.php
new file mode 100644
index 000000000..49a44899a
--- /dev/null
+++ b/application/admin/controller/Tags.php
@@ -0,0 +1,159 @@
+title = '微信粉丝标签管理';
+ list($get, $db) = [$this->request->get(), Db::name($this->table)];
+ foreach (['name'] as $key) {
+ (isset($get[$key]) && $get[$key] !== '') && $db->whereLike($key, "%{$get[$key]}%");
+ }
+ return parent::_list($db->order('id asc'));
+ }
+
+ /**
+ * 添加粉丝标签
+ * @return array|string
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\exception\PDOException
+ */
+ public function add()
+ {
+ if ($this->request->isGet()) {
+ return parent::_form($this->table, 'form', 'id');
+ }
+ $name = $this->request->post('name', '');
+ empty($name) && $this->error('粉丝标签名不能为空!');
+ if (Db::name($this->table)->where('name', $name)->count() > 0) {
+ $this->error('粉丝标签标签名已经存在, 请使用其它标签名!');
+ }
+ $wechat = WechatService::tags();
+ if (false === ($result = $wechat->createTags($name)) && isset($result['tag'])) {
+ $this->error("添加粉丝标签失败. ");
+ }
+ $result['tag']['count'] = 0;
+ if (DataService::save($this->table, $result['tag'], 'id')) {
+ $this->success('添加粉丝标签成功!', '');
+ }
+ $this->error('粉丝标签添加失败, 请稍候再试!');
+ }
+
+ /**
+ * 编辑粉丝标签
+ * @return array|string
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ public function edit()
+ {
+ // 显示编辑界面
+ if ($this->request->isGet()) {
+ return parent::_form($this->table, 'form', 'id');
+ }
+ // 接收提交的数据
+ $id = $this->request->post('id', '0');
+ $name = $this->request->post('name', '');
+ $info = Db::name($this->table)->where(['name' => $name])->find();
+ if (!empty($info)) {
+ if (intval($info['id']) === intval($id)) {
+ $this->error('粉丝标签名没有改变, 无需修改!');
+ }
+ $this->error('标签已经存在, 使用其它名称再试!');
+ }
+ try {
+ WechatService::tags()->updateTags($id, $name);
+ DataService::save($this->table, ['id' => $id, 'name' => $name], 'id');
+ } catch (\Exception $e) {
+ $this->error('编辑标签失败, 请稍后再试!' . $e->getMessage());
+ }
+ $this->success('编辑标签成功!', '');
+ }
+
+
+ /**
+ * 删除粉丝标签
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function del()
+ {
+ $wechat = WechatService::tags();
+ foreach (explode(',', $this->request->post('id', '')) as $id) {
+ if ($wechat->deleteTags($id)) {
+ Db::name('WechatFansTags')->where(['id' => $id])->delete();
+ } else {
+ $this->error('移除粉丝标签失败,请稍候再试!');
+ }
+ }
+ $this->success('移除粉丝标签成功!', '');
+ }
+
+ /**
+ * 同步粉丝标签列表
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function sync()
+ {
+ Db::name($this->table)->where('1=1')->delete();
+ if (TagsService::sync()) {
+ LogService::write('微信管理', '同步全部微信粉丝标签成功');
+ $this->success('同步获取所有粉丝标签成功!', '');
+ }
+ $this->error('同步获取粉丝标签失败, 请稍候再!');
+ }
+
+}
diff --git a/application/admin/controller/api/Push.php b/application/admin/controller/api/Push.php
new file mode 100644
index 000000000..c1a847b49
--- /dev/null
+++ b/application/admin/controller/api/Push.php
@@ -0,0 +1,257 @@
+
+ */
+class Push
+{
+
+ /**
+ * 当前公众号APPID
+ * @var string
+ */
+ protected $appid;
+
+ /**
+ * 当前微信用户openid
+ * @var string
+ */
+ protected $openid;
+
+ /**
+ * 当前微信消息对象
+ * @var array
+ */
+ protected $receive;
+
+ /**
+ * 微信消息接口
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public function __construct()
+ {
+ $request = app('request');
+ $this->appid = $request->post('appid', '', null);
+ $this->openid = $request->post('openid', '', null);
+ $this->receive = unserialize($request->post('receive', '', null));
+ p($this->receive);
+ if (empty($this->appid) || empty($this->openid) || empty($this->receive)) {
+ throw new Exception('微信API实例缺失必要参数[appid,openid,event].');
+ }
+ if ($this->appid !== sysconf('wechat_appid')) {
+ throw new Exception('微信API实例APPID验证失败.');
+ }
+ // text,event,image,location
+ if (method_exists($this, ($method = $this->receive['MsgType']))) {
+ $this->$method();
+ }
+ }
+
+ /**
+ * 文件消息处理
+ * @return bool
+ * @throws \Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ protected function text()
+ {
+ return $this->keys("wechat_keys#keys#{$this->receive['Content']}");
+ }
+
+ /**
+ * 事件消息处理
+ * @return bool|string
+ * @throws \Exception
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\exception\PDOException
+ */
+ protected function event()
+ {
+ switch (strtolower($this->receive['Event'])) {
+ case 'subscribe':
+ $this->updateFansinfo(true);
+ if (isset($this->receive['EventKey']) && is_string($this->receive['EventKey'])) {
+ if (($key = preg_replace('/^qrscene_/i', '', $this->receive['EventKey']))) {
+ [$this->updateSpread($key), $this->keys("wechat_keys#keys#{$key}")];
+ }
+ }
+ return $this->keys('wechat_keys#keys#subscribe', true);
+ case 'unsubscribe':
+ return $this->updateFansinfo(false);
+ case 'click':
+ return $this->keys($this->receive['EventKey']);
+ case 'scancode_push':
+ case 'scancode_waitmsg':
+ if (isset($this->receive['ScanCodeInfo'])) {
+ $this->receive['ScanCodeInfo'] = (array)$this->receive['ScanCodeInfo'];
+ if (!empty($this->receive['ScanCodeInfo']['ScanResult'])) {
+ return $this->keys("wechat_keys#keys#{$this->receive['ScanCodeInfo']['ScanResult']}");
+ }
+ }
+ return false;
+ case 'scan':
+ if (!empty($this->receive['EventKey'])) {
+ return $this->keys("wechat_keys#keys#{$this->receive['EventKey']}");
+ }
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * 关键字处理
+ * @param string $rule 关键字规则
+ * @param bool $isLastReply 强制结束
+ * @return bool
+ * @throws \Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ */
+ protected function keys($rule, $isLastReply = false)
+ {
+ list($table, $field, $value) = explode('#', $rule . '##');
+ $info = Db::name($table)->where($field, $value)->find();
+ p($info);
+ if (empty($info['type']) || (array_key_exists('status', $info) && empty($info['status']))) {
+ // 切换默认回复
+ return $isLastReply ? false : $this->keys('wechat_keys#keys#default', true);
+ }
+ switch ($info['type']) {
+ case 'customservice':
+ return $this->sendMessage('text', ['content' => $info['content']]);
+ case 'keys':
+ $content = empty($info['content']) ? $info['name'] : $info['content'];
+ return $this->keys("wechat_keys#keys#{$content}");
+ case 'text':
+ return $this->sendMessage('text', ['content' => $info['content']]);
+ case 'news':
+ list($news, $data) = [MediaService::getNewsById($info['news_id']), []];
+ if (empty($news['articles'])) {
+ return false;
+ }
+ foreach ($news['articles'] as $vo) {
+ $url = url("@wechat/review", '', true, true) . "?content={$vo['id']}&type=article";
+ $data[] = ['url' => $url, 'title' => $vo['title'], 'picurl' => $vo['local_url'], 'description' => $vo['digest']];
+ }
+ return $this->sendMessage('news', ['articles' => $data]);
+ case 'music':
+ if (empty($info['music_url']) || empty($info['music_title']) || empty($info['music_desc'])) {
+ return false;
+ }
+ $media_id = empty($info['music_image']) ? '' : MediaService::uploadForeverMedia($info['music_image'], 'image');
+ $data = ['title' => $info['music_title'], 'description' => $info['music_desc'], 'musicurl' => $info['music_url'], 'hqmusicurl' => $info['music_url'], 'thumb_media_id' => $media_id];
+ return $this->sendMessage('music', $data);
+ case 'voice':
+ if (empty($info['voice_url']) || !($media_id = MediaService::uploadForeverMedia($info['voice_url'], 'voice'))) {
+ return false;
+ }
+ return $this->sendMessage('voice', ['media_id' => $media_id]);
+ case 'image':
+ if (empty($info['image_url']) || !($media_id = MediaService::uploadForeverMedia($info['image_url'], 'image'))) {
+ return false;
+ }
+ return $this->sendMessage('image', ['media_id' => $media_id]);
+ case 'video':
+ if (empty($info['video_url']) || empty($info['video_desc']) || empty($info['video_title'])) {
+ return false;
+ }
+ $videoData = ['title' => $info['video_title'], 'introduction' => $info['video_desc']];
+ if (!($media_id = MediaService::uploadForeverMedia($info['video_url'], 'video', $videoData))) {
+ return false;
+ }
+ $data = ['media_id' => $media_id, 'title' => $info['video_title'], 'description' => $info['video_desc']];
+ return $this->sendMessage('video', $data);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * 发送消息到公众号
+ * @param string $type 消息类型(text|image|voice|video|music|news|mpnews|wxcard)
+ * @param array $data 消息内容
+ * @return array|bool
+ * @throws \Exception
+ */
+ protected function sendMessage($type, $data)
+ {
+ $msgData = ['touser' => $this->openid, 'msgtype' => $type, "{$type}" => $data];
+ p($msgData);
+ return WechatService::custom()->send($msgData);
+ }
+
+ /**
+ * 更新推荐二维码关系
+ * @param string $key
+ * @return bool
+ * @throws \think\Exception
+ * @throws \think\db\exception\DataNotFoundException
+ * @throws \think\db\exception\ModelNotFoundException
+ * @throws \think\exception\DbException
+ * @throws \think\exception\PDOException
+ */
+ protected function updateSpread($key)
+ {
+ // 检测推荐是否有效
+ $fans = Db::name('WechatFans')->where(['openid' => $key])->find();
+ if (empty($fans['openid']) || $fans['openid'] === $this->openid) {
+ return false;
+ }
+ // 标识推荐关系
+ $data = ['spread_openid' => $fans['openid'], 'spread_at' => date('Y-m-d H:i:s')];
+ $where = "openid='{$this->openid}' and (spread_openid is null or spread_openid='')";
+ return Db::name('WechatFans')->where($where)->update($data) !== false;
+ }
+
+ /**
+ * 同步粉丝状态
+ * @param bool $subscribe 关注状态
+ * @return string
+ * @throws \Exception
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ protected function updateFansinfo($subscribe = true)
+ {
+ if ($subscribe) {
+ $userInfo = WechatService::user()->getUserInfo($this->openid);
+ $userInfo['subscribe'] = intval($subscribe);
+ FansService::set($userInfo);
+ } else {
+ $fans = ['subscribe' => '0', 'openid' => $this->openid, 'appid' => $this->appid];
+ DataService::save('WechatFans', $fans, 'openid', ['appid' => $this->appid]);
+ }
+ }
+
+}
diff --git a/application/admin/service/FansService.php b/application/admin/service/FansService.php
new file mode 100644
index 000000000..d7732cbb7
--- /dev/null
+++ b/application/admin/service/FansService.php
@@ -0,0 +1,123 @@
+ $openid, 'appid' => sysconf('wechat_appid')];
+ $user = Db::name('WechatFans')->where($map)->find();
+ foreach (['country', 'province', 'city', 'nickname', 'remark'] as $k) {
+ isset($user[$k]) && $user[$k] = ToolsService::emojiDecode($user[$k]);
+ }
+ return $user;
+ }
+
+ /**
+ * 同步所有粉丝记录
+ * @param string $next_openid
+ * @return bool
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public static function sync($next_openid = '')
+ {
+ $wechat = WechatService::user();
+ $result = $wechat->getUserList($next_openid);
+ if (empty($result['data']['openid'])) {
+ return false;
+ }
+ foreach (array_chunk($result['data']['openid'], 100) as $openids) {
+ foreach ($wechat->getBatchUserInfo($openids)['user_info_list'] as $user) {
+ if (false === self::set($user)) {
+ return false;
+ }
+ if ($result['next_openid'] === $user['openid']) {
+ unset($result['next_openid']);
+ }
+ }
+ }
+ return empty($result['next_openid']) ? true : self::sync($result['next_openid']);
+ }
+
+ /**
+ * 同步获取黑名单信息
+ * @param string $next_openid
+ * @return bool
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public static function syncBlack($next_openid = '')
+ {
+ $wechat = WechatService::user();
+ $result = $wechat->getBlackList($next_openid);
+ foreach (array_chunk($result['data']['openid'], 100) as $openids) {
+ $info = $wechat->getBatchUserInfo($openids);
+ foreach ($info as $user) {
+ $user['is_black'] = '1';
+ if (self::set($user) && $result['next_openid'] === $user['openid']) {
+ unset($result['next_openid']);
+ }
+ }
+ }
+ return empty($result['next_openid']) ? true : self::syncBlack($result['next_openid']);
+ }
+
+}
\ No newline at end of file
diff --git a/application/admin/service/MediaService.php b/application/admin/service/MediaService.php
new file mode 100644
index 000000000..342c0dbf7
--- /dev/null
+++ b/application/admin/service/MediaService.php
@@ -0,0 +1,101 @@
+where(['id' => $id])->where($where)->find();
+ $article_ids = explode(',', $data['article_id']);
+ $articles = Db::name('WechatNewsArticle')->whereIn('id', $article_ids)->select();
+ $data['articles'] = [];
+ foreach ($article_ids as $article_id) {
+ foreach ($articles as $article) {
+ if (intval($article['id']) === intval($article_id)) {
+ unset($article['create_by'], $article['create_at']);
+ $article['content'] = htmlspecialchars_decode($article['content']);
+ $data['articles'][] = $article;
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 上传图片到微信服务器
+ * @param string $local_url 图文地址
+ * @return string
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ */
+ public static function uploadImage($local_url)
+ {
+ $media_url = Db::name('WechatNewsImage')->where(['md5' => md5($local_url)])->value('media_url');
+ if (!empty($media_url)) {
+ return $media_url;
+ }
+ $result = WechatService::wechat()->upFile(base64_encode(file_get_contents($local_url)), $local_url);
+ $info = WechatService::media()->uploadImg($result['file']);
+ WechatService::wechat()->rmFile($local_url);
+ $data = ['local_url' => $local_url, 'media_url' => $info['url'], 'md5' => md5($local_url)];
+ Db::name('WechatNewsImage')->insert($data);
+ return $info['url'];
+ }
+
+ /**
+ * 上传图片永久素材,返回素材media_id
+ * @param string $local_url 文件URL地址
+ * @param string $type 文件类型
+ * @param array $video_info 视频信息
+ * @return string|null
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public static function uploadForeverMedia($local_url, $type = 'image', $video_info = [])
+ {
+ $map = ['md5' => md5($local_url), 'appid' => sysconf('wechat_appid')];
+ if (($media_id = Db::name('WechatNewsMedia')->where($map)->value('media_id'))) {
+ return $media_id;
+ }
+ $result = WechatService::wechat()->upFile(base64_encode(file_get_contents($local_url)), $local_url);
+ $result = WechatService::media()->addMaterial($result['file'], $type, $video_info);
+ WechatService::wechat()->rmFile($local_url);
+ $data = ['md5' => $map['md5'], 'type' => $type, 'appid' => $map['appid'], 'media_id' => $result['media_id'], 'local_url' => $local_url];
+ isset($result['url']) && $data['media_url'] = $result['url'];
+ Db::name('WechatNewsMedia')->insert($data);
+ return $data['media_id'];
+ }
+
+}
\ No newline at end of file
diff --git a/application/admin/service/TagsService.php b/application/admin/service/TagsService.php
new file mode 100644
index 000000000..52ec49ffc
--- /dev/null
+++ b/application/admin/service/TagsService.php
@@ -0,0 +1,69 @@
+getUserTagId($openid);
+ if (!is_array($tagsid)) {
+ return false;
+ }
+ $data = ['openid' => $openid, 'tagid_list' => join(',', $tagsid)];
+ return DataService::save('WechatFans', $data, 'openid', ['appid' => sysconf('wechat_appid')]);
+ }
+
+ /**
+ * 从微信服务器获取所有标签
+ * @return bool
+ * @throws \WeChat\Exceptions\InvalidResponseException
+ * @throws \WeChat\Exceptions\LocalCacheException
+ * @throws \think\Exception
+ * @throws \think\exception\PDOException
+ */
+ public static function sync()
+ {
+ $appid = sysconf('wechat_appid');
+ $result = WechatService::tags()->getTags();
+ Db::name('WechatFansTags')->where(['appid' => $appid])->delete();
+ foreach (array_chunk($result['tags'], 100) as $list) {
+ foreach ($list as &$vo) {
+ $vo['appid'] = $appid;
+ }
+ Db::name('WechatFansTags')->insertAll($list);
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/application/admin/view/block/index.html b/application/admin/view/block/index.html
new file mode 100644
index 000000000..f79abcfc8
--- /dev/null
+++ b/application/admin/view/block/index.html
@@ -0,0 +1,160 @@
+{extend name='admin@public/content'}
+
+{block name="button"}
+
+
+
+
+
+
+
+
+
+{/block}
+
+{block name="content"}
+
+
{$v.title}
{/if} +没 有 记 录 哦!
+ {/if} +{$page|raw}
{/if} + +{/block} + +{block name='script'} + +{/block} + +{block name="style"} + +{/block} \ No newline at end of file diff --git a/application/admin/view/news/push.html b/application/admin/view/news/push.html new file mode 100644 index 000000000..67fba17b0 --- /dev/null +++ b/application/admin/view/news/push.html @@ -0,0 +1,192 @@ + + + + + \ No newline at end of file diff --git a/application/admin/view/news/select.html b/application/admin/view/news/select.html new file mode 100644 index 000000000..ebb2a4543 --- /dev/null +++ b/application/admin/view/news/select.html @@ -0,0 +1,80 @@ +{extend name='admin@public/main'} + +{block name='style'} + +{/block} + +{block name="body"} +{$v.title}
{/if} +没 有 记 录 哦!
{/if} ++ |