diff --git a/CHANGELOG.md b/CHANGELOG.md index 50174e64..f42c92f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +### [v1.2.2](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.2)(2020-12-24) + +#### 增加 + +- 登录账户微信提醒 +- 购买成功微信提醒 +- 退款成功微信提醒 +- 开始直播微信提醒 +- 咨询回复微信提醒 +- 咨询回复短信提醒 + +#### 修复 + +- 创建章节,关联表数据没有生成 +- 创建群组,没有生成max_im_group_id缓存 +- 课程分类列表没有过滤掉帮助分类的内容 +- 创建角色字段routes MySQL text 类型报错 +- 低品质视频无法播放 +- 后台遗漏的权限 + ### [v1.2.1](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.1)(2020-12-10) - 增加QQ,微信,微博第三方登录 - 代码优化以及问题修复 diff --git a/README.md b/README.md index a6a49470..3a7b980b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ 酷瓜云课堂,依托腾讯云基础服务架构,采用C扩展框架Phalcon开发,GPL-2.0开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。 -![](https://img.shields.io/static/v1?label=release&message=1.2.1&color=blue) -![](https://img.shields.io/static/v1?label=stars&message=112&color=blue) -![](https://img.shields.io/static/v1?label=forks&message=41&color=blue) +![](https://img.shields.io/static/v1?label=release&message=1.2.2&color=blue) +![](https://img.shields.io/static/v1?label=stars&message=136&color=blue) +![](https://img.shields.io/static/v1?label=forks&message=50&color=blue) ![](https://img.shields.io/static/v1?label=license&message=GPL-2.0&color=blue) #### 系统功能 @@ -86,3 +86,5 @@ Tips: 测试支付请用手机号注册一个新账户,以便接收订单通 - 系统定制 - 企业授权 +毫无保留的真开源不容易,如果对你有帮助,请给我们 **STAR** !!! + diff --git a/app/Builders/ConsultList.php b/app/Builders/ConsultList.php index 44aa3f7e..55f27c0f 100644 --- a/app/Builders/ConsultList.php +++ b/app/Builders/ConsultList.php @@ -26,6 +26,7 @@ class ConsultList extends Builder foreach ($consults as $key => $consult) { $consults[$key]['owner'] = $users[$consult['owner_id']] ?? new \stdClass(); + $consults[$key]['replier'] = $users[$consult['replier_id']] ?? new \stdClass(); } return $consults; @@ -67,7 +68,9 @@ class ConsultList extends Builder public function getUsers(array $consults) { - $ids = kg_array_column($consults, 'owner_id'); + $ownerIds = kg_array_column($consults, 'owner_id'); + $replierIds = kg_array_column($consults, 'replier_id'); + $ids = array_merge($ownerIds, $replierIds); $userRepo = new UserRepo(); diff --git a/app/Console/Tasks/CleanLogTask.php b/app/Console/Tasks/CleanLogTask.php index b4dd7ee9..d4d532c7 100644 --- a/app/Console/Tasks/CleanLogTask.php +++ b/app/Console/Tasks/CleanLogTask.php @@ -13,6 +13,7 @@ class CleanLogTask extends Task $this->cleanSqlLog(); $this->cleanListenLog(); $this->cleanCaptchaLog(); + $this->cleanWechatLog(); $this->cleanMailLog(); $this->cleanSmsLog(); $this->cleanVodLog(); @@ -22,6 +23,7 @@ class CleanLogTask extends Task $this->cleanWxpayLog(); $this->cleanOrderLog(); $this->cleanRefundLog(); + $this->cleanNoticeLog(); } /** @@ -112,6 +114,14 @@ class CleanLogTask extends Task $this->cleanLog('mail', 7); } + /** + * 清理微信服务日志 + */ + protected function cleanWechatLog() + { + $this->cleanLog('wechat', 7); + } + /** * 清理阿里支付服务日志 */ @@ -144,6 +154,14 @@ class CleanLogTask extends Task $this->cleanLog('refund', 30); } + /** + * 清理通知日志 + */ + protected function cleanNoticeLog() + { + $this->cleanLog('notice', 7); + } + /** * 清理日志文件 * diff --git a/app/Console/Tasks/DeliverTask.php b/app/Console/Tasks/DeliverTask.php index 1895ce26..ad7c846b 100644 --- a/app/Console/Tasks/DeliverTask.php +++ b/app/Console/Tasks/DeliverTask.php @@ -12,7 +12,7 @@ use App\Repos\ImGroup as ImGroupRepo; use App\Repos\ImGroupUser as ImGroupUserRepo; use App\Repos\Order as OrderRepo; use App\Repos\User as UserRepo; -use App\Services\Sms\Order as OrderSms; +use App\Services\Logic\Notice\OrderFinish as OrderFinishNotice; use Phalcon\Mvc\Model; use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\ResultsetInterface; @@ -26,7 +26,7 @@ class DeliverTask extends Task { $logger = $this->getLogger('order'); - $tasks = $this->findTasks(); + $tasks = $this->findTasks(30); if ($tasks->count() == 0) { return; @@ -84,7 +84,7 @@ class DeliverTask extends Task } if ($task->status == TaskModel::STATUS_FINISHED) { - $this->handleOrderNotice($order); + $this->handleOrderFinishNotice($order); } elseif ($task->status == TaskModel::STATUS_FAILED) { $this->handleOrderRefund($order); } @@ -199,11 +199,11 @@ class DeliverTask extends Task } } - protected function handleOrderNotice(OrderModel $order) + protected function handleOrderFinishNotice(OrderModel $order) { - $sms = new OrderSms(); + $notice = new OrderFinishNotice(); - $sms->handle($order); + $notice->createTask($order); } protected function handleOrderRefund(OrderModel $order) @@ -244,7 +244,7 @@ class DeliverTask extends Task * @param int $limit * @return ResultsetInterface|Resultset|TaskModel[] */ - protected function findTasks($limit = 100) + protected function findTasks($limit = 30) { $itemType = TaskModel::TYPE_DELIVER; $status = TaskModel::STATUS_PENDING; diff --git a/app/Console/Tasks/LiveNotifyTask.php b/app/Console/Tasks/LiveNotifyTask.php deleted file mode 100644 index ab6e2241..00000000 --- a/app/Console/Tasks/LiveNotifyTask.php +++ /dev/null @@ -1,81 +0,0 @@ -getRedis(); - - $service = new LiveNotifyService(); - - $key = $service->getNotifyKey(); - - $chapterIds = $redis->sMembers($key); - - if (!$chapterIds) return; - - $sentKey = $service->getSentNotifyKey(); - - $sentChapterIds = $redis->sMembers($sentKey); - - foreach ($chapterIds as $chapterId) { - if (!in_array($chapterId, $sentChapterIds)) { - $this->sendNotification($chapterId); - } else { - $redis->sAdd($sentKey, $chapterId); - } - } - - if ($redis->sCard($sentKey) == 1) { - $redis->expire($sentKey, 86400); - } - } - - protected function sendNotification($chapterId) - { - $chapterRepo = new ChapterRepo(); - - $chapterLive = $chapterRepo->findChapterLive($chapterId); - - if (!$chapterLive) return; - - $targetUserIds = $this->findTargetUserIds($chapterLive->course_id); - - if (!$targetUserIds) return; - - $sms = new LiveSms(); - - foreach ($targetUserIds as $userId) { - $sms->handle($chapterId, $userId, $chapterLive->start_time); - } - } - - protected function findTargetUserIds($courseId) - { - $sourceTypes = [ - CourseUserModel::SOURCE_CHARGE, - CourseUserModel::SOURCE_VIP, - ]; - - $rows = CourseUserModel::query() - ->where('course_id = :course_id:', ['course_id' => $courseId]) - ->andWhere('role_type = :role_type:', ['role_type' => CourseUserModel::ROLE_STUDENT]) - ->inWhere('source_type', $sourceTypes) - ->execute(); - - if ($rows->count() == 0) { - return []; - } - - return kg_array_column($rows->toArray(), 'user_id'); - } - -} diff --git a/app/Console/Tasks/MaintainTask.php b/app/Console/Tasks/MaintainTask.php index 9ac60365..7f6d5898 100644 --- a/app/Console/Tasks/MaintainTask.php +++ b/app/Console/Tasks/MaintainTask.php @@ -19,7 +19,7 @@ class MaintainTask extends Task * 重建首页课程缓存 * * @param array $params - * @command: php console.php maintain reset_index_course_cache + * @command: php console.php maintain rebuild_index_course_cache */ public function rebuildIndexCourseCacheAction($params) { diff --git a/app/Console/Tasks/NoticeTask.php b/app/Console/Tasks/NoticeTask.php new file mode 100644 index 00000000..c4a385d4 --- /dev/null +++ b/app/Console/Tasks/NoticeTask.php @@ -0,0 +1,137 @@ +getLogger('notice'); + + $tasks = $this->findTasks(300); + + if ($tasks->count() == 0) { + return; + } + + foreach ($tasks as $task) { + + try { + + switch ($task->item_type) { + case TaskModel::TYPE_NOTICE_ACCOUNT_LOGIN: + $this->handleAccountLoginNotice($task); + break; + case TaskModel::TYPE_NOTICE_LIVE_BEGIN: + $this->handleLiveBeginNotice($task); + break; + case TaskModel::TYPE_NOTICE_ORDER_FINISH: + $this->handleOrderFinishNotice($task); + break; + case TaskModel::TYPE_NOTICE_REFUND_FINISH: + $this->handleRefundFinishNotice($task); + break; + case TaskModel::TYPE_NOTICE_CONSULT_REPLY: + $this->handleConsultReplyNotice($task); + break; + } + + $task->status = TaskModel::STATUS_FINISHED; + + $task->update(); + + } catch (\Exception $e) { + + $task->try_count += 1; + $task->priority += 1; + + if ($task->try_count > self::TRY_COUNT) { + $task->status = TaskModel::STATUS_FAILED; + } + + $task->update(); + + $logger->info('Notice Process Exception ' . kg_json_encode([ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + 'task' => $task->toArray(), + ])); + } + } + } + + protected function handleAccountLoginNotice(TaskModel $task) + { + $notice = new AccountLoginNotice(); + + return $notice->handleTask($task); + } + + protected function handleLiveBeginNotice(TaskModel $task) + { + $notice = new LiveBeginNotice(); + + return $notice->handleTask($task); + } + + protected function handleOrderFinishNotice(TaskModel $task) + { + $notice = new OrderFinishNotice(); + + return $notice->handleTask($task); + } + + protected function handleRefundFinishNotice(TaskModel $task) + { + $notice = new RefundFinishNotice(); + + return $notice->handleTask($task); + } + + protected function handleConsultReplyNotice(TaskModel $task) + { + $notice = new ConsultReplyNotice(); + + return $notice->handleTask($task); + } + + /** + * @param int $limit + * @return ResultsetInterface|Resultset|TaskModel[] + */ + protected function findTasks($limit = 100) + { + $itemTypes = [ + TaskModel::TYPE_NOTICE_ACCOUNT_LOGIN, + TaskModel::TYPE_NOTICE_LIVE_BEGIN, + TaskModel::TYPE_NOTICE_ORDER_FINISH, + TaskModel::TYPE_NOTICE_REFUND_FINISH, + TaskModel::TYPE_NOTICE_CONSULT_REPLY, + ]; + + $status = TaskModel::STATUS_PENDING; + + $tryCount = self::TRY_COUNT; + + return TaskModel::query() + ->inWhere('item_type', $itemTypes) + ->andWhere('status = :status:', ['status' => $status]) + ->andWhere('try_count < :try_count:', ['try_count' => $tryCount + 1]) + ->orderBy('priority ASC') + ->limit($limit) + ->execute(); + } + +} diff --git a/app/Console/Tasks/RefundTask.php b/app/Console/Tasks/RefundTask.php index 0edf1a67..1697a4e1 100644 --- a/app/Console/Tasks/RefundTask.php +++ b/app/Console/Tasks/RefundTask.php @@ -11,9 +11,9 @@ use App\Repos\Order as OrderRepo; use App\Repos\Refund as RefundRepo; use App\Repos\Trade as TradeRepo; use App\Repos\User as UserRepo; +use App\Services\Logic\Notice\RefundFinish as RefundFinishNotice; use App\Services\Pay\Alipay as AlipayService; use App\Services\Pay\Wxpay as WxpayService; -use App\Services\Sms\Refund as RefundSms; use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\ResultsetInterface; @@ -29,7 +29,7 @@ class RefundTask extends Task { $logger = $this->getLogger('refund'); - $tasks = $this->findTasks(); + $tasks = $this->findTasks(30); if ($tasks->count() == 0) { return; @@ -95,7 +95,7 @@ class RefundTask extends Task $this->db->commit(); - $this->handleRefundNotice($refund); + $this->handleRefundFinishNotice($refund); } catch (\Exception $e) { @@ -259,7 +259,7 @@ class RefundTask extends Task } /** - * 处理测试订单退款 + * 处理赞赏订单退款 * * @param OrderModel $order */ @@ -281,11 +281,11 @@ class RefundTask extends Task /** * @param RefundModel $refund */ - protected function handleRefundNotice(RefundModel $refund) + protected function handleRefundFinishNotice(RefundModel $refund) { - $sms = new RefundSms(); + $notice = new RefundFinishNotice(); - $sms->handle($refund); + $notice->createTask($refund); } /** diff --git a/app/Console/Tasks/Task.php b/app/Console/Tasks/Task.php index 350cb5ec..75944d41 100644 --- a/app/Console/Tasks/Task.php +++ b/app/Console/Tasks/Task.php @@ -30,7 +30,7 @@ class Task extends \Phalcon\Cli\Task return $appService->getRedis(); } - public function getLogger($channel = null) + public function getLogger($channel = 'console') { $appService = new AppService(); diff --git a/app/Http/Admin/Controllers/SettingController.php b/app/Http/Admin/Controllers/SettingController.php index 71e41fc0..309d6bd8 100644 --- a/app/Http/Admin/Controllers/SettingController.php +++ b/app/Http/Admin/Controllers/SettingController.php @@ -327,4 +327,29 @@ class SettingController extends Controller } } + /** + * @Route("/wechat", name="admin.setting.wechat") + */ + public function wechatAction() + { + $settingService = new SettingService(); + + if ($this->request->isPost()) { + + $section = $this->request->getPost('section', 'string'); + + $data = $this->request->getPost(); + + $settingService->updateWechatSettings($section, $data); + + return $this->jsonSuccess(['msg' => '更新配置成功']); + + } else { + + $oa = $settingService->getWechatOASettings(); + + $this->view->setVar('oa', $oa); + } + } + } diff --git a/app/Http/Admin/Services/AuthNode.php b/app/Http/Admin/Services/AuthNode.php index 427c9924..db2ec7d8 100644 --- a/app/Http/Admin/Services/AuthNode.php +++ b/app/Http/Admin/Services/AuthNode.php @@ -34,14 +34,14 @@ class AuthNode extends Service 'title' => '分类列表', 'type' => 'button', 'route' => 'admin.category.list', - 'params' => ['type' => 'course'], + 'params' => ['type' => 1], ], [ 'id' => '1-2-2', 'title' => '添加分类', 'type' => 'button', 'route' => 'admin.category.add', - 'params' => ['type' => 'course'], + 'params' => ['type' => 1], ], [ 'id' => '1-2-3', @@ -90,7 +90,7 @@ class AuthNode extends Service 'id' => '1-1-5', 'title' => '删除课程', 'type' => 'button', - 'route' => 'admin.course.edit', + 'route' => 'admin.course.delete', ], [ 'id' => '1-1-6', @@ -529,6 +529,12 @@ class AuthNode extends Service ], [ 'id' => '3-2-3', + 'title' => '交易详情', + 'type' => 'button', + 'route' => 'admin.trade.show', + ], + [ + 'id' => '3-2-4', 'title' => '交易退款', 'type' => 'button', 'route' => 'admin.trade.refund', @@ -750,6 +756,12 @@ class AuthNode extends Service 'type' => 'menu', 'route' => 'admin.setting.oauth', ], + [ + 'id' => '5-1-13', + 'title' => '微信公众平台', + 'type' => 'menu', + 'route' => 'admin.setting.wechat', + ], ], ], ], diff --git a/app/Http/Admin/Services/Consult.php b/app/Http/Admin/Services/Consult.php index 8625cf86..667b99a7 100644 --- a/app/Http/Admin/Services/Consult.php +++ b/app/Http/Admin/Services/Consult.php @@ -4,8 +4,10 @@ namespace App\Http\Admin\Services; use App\Builders\ConsultList as ConsultListBuilder; use App\Library\Paginator\Query as PagerQuery; +use App\Models\Consult as ConsultModel; use App\Repos\Consult as ConsultRepo; use App\Repos\Course as CourseRepo; +use App\Services\Logic\Notice\ConsultReply as ConsultReplyNotice; use App\Validators\Consult as ConsultValidator; class Consult extends Service @@ -52,12 +54,18 @@ class Consult extends Service $data = []; + $firstReply = false; + if (!empty($post['question'])) { $data['question'] = $validator->checkQuestion($post['question']); } if (!empty($post['answer'])) { $data['answer'] = $validator->checkAnswer($post['answer']); + $data['reply_time'] = time(); + if ($consult->reply_time == 0) { + $firstReply = true; + } } if (isset($post['private'])) { @@ -70,6 +78,10 @@ class Consult extends Service $consult->update($data); + if ($firstReply) { + $this->handleReplyNotice($consult); + } + return $consult; } @@ -107,6 +119,13 @@ class Consult extends Service $course->update(); } + protected function handleReplyNotice(ConsultModel $consult) + { + $notice = new ConsultReplyNotice(); + + $notice->createTask($consult); + } + protected function findOrFail($id) { $validator = new ConsultValidator(); diff --git a/app/Http/Admin/Services/Role.php b/app/Http/Admin/Services/Role.php index 82fde39a..c9459b33 100644 --- a/app/Http/Admin/Services/Role.php +++ b/app/Http/Admin/Services/Role.php @@ -61,8 +61,11 @@ class Role extends Service $data['name'] = $validator->checkName($post['name']); $data['summary'] = $validator->checkSummary($post['summary']); - $data['routes'] = $validator->checkRoutes($post['routes']); - $data['routes'] = $this->handleRoutes($data['routes']); + + if (isset($post['routes'])) { + $data['routes'] = $validator->checkRoutes($post['routes']); + $data['routes'] = $this->handleRoutes($data['routes']); + } $role->update($data); @@ -114,9 +117,9 @@ class Role extends Service * @param array $routes * @return array */ - protected function handleRoutes($routes) + protected function handleRoutes(array $routes) { - if (empty($routes)) { + if (count($routes) == 0) { return []; } @@ -140,23 +143,24 @@ class Role extends Service if (in_array('admin.course.list', $routes)) { $list[] = 'admin.course.chapters'; $list[] = 'admin.chapter.lessons'; + $list[] = 'admin.chapter.resources'; } if (array_intersect(['admin.course.add', 'admin.course.edit'], $routes)) { $list[] = 'admin.chapter.add'; $list[] = 'admin.chapter.edit'; + $list[] = 'admin.chapter.create'; + $list[] = 'admin.chapter.update'; $list[] = 'admin.chapter.content'; - } - - if (array_intersect(['admin.chapter.add', 'admin.chapter.edit'], $routes)) { $list[] = 'admin.resource.create'; $list[] = 'admin.resource.update'; - $list[] = 'admin.resource.delete'; } if (in_array('admin.course.delete', $routes)) { $list[] = 'admin.chapter.delete'; $list[] = 'admin.chapter.restore'; + $list[] = 'admin.resource.delete'; + $list[] = 'admin.resource.restore'; } if (in_array('admin.category.list', $routes)) { @@ -172,6 +176,18 @@ class Role extends Service $list[] = 'admin.category.list'; } + if (in_array('admin.order.show', $routes)) { + $list[] = 'admin.order.status_history'; + } + + if (in_array('admin.trade.show', $routes)) { + $list[] = 'admin.trade.status_history'; + } + + if (in_array('admin.refund.show', $routes)) { + $list[] = 'admin.refund.status_history'; + } + $list = array_unique($list); return array_values($list); diff --git a/app/Http/Admin/Services/Session.php b/app/Http/Admin/Services/Session.php index 2bff96d1..fae5222b 100644 --- a/app/Http/Admin/Services/Session.php +++ b/app/Http/Admin/Services/Session.php @@ -2,7 +2,9 @@ namespace App\Http\Admin\Services; +use App\Models\User as UserModel; use App\Services\Auth\Admin as AdminAuth; +use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService; use App\Validators\Account as AccountValidator; use App\Validators\Captcha as CaptchaValidator; @@ -45,6 +47,8 @@ class Session extends Service $captchaValidator->checkCode($post['ticket'], $post['rand']); } + $this->handleLoginNotice($user); + $this->auth->saveAuthInfo($user); } @@ -53,4 +57,11 @@ class Session extends Service $this->auth->clearAuthInfo(); } + protected function handleLoginNotice(UserModel $user) + { + $service = new AccountLoginNoticeService(); + + $service->createTask($user); + } + } diff --git a/app/Http/Admin/Services/Setting.php b/app/Http/Admin/Services/Setting.php index 3d2130fd..3f2abe3e 100644 --- a/app/Http/Admin/Services/Setting.php +++ b/app/Http/Admin/Services/Setting.php @@ -57,6 +57,16 @@ class Setting extends Service return $wxpay; } + public function getWechatOASettings() + { + $oa = $this->getSettings('wechat.oa'); + + $oa['auth_url'] = $oa['auth_url'] ?: kg_full_url(['for' => 'home.wechat.oa.auth_callback']); + $oa['notify_url'] = $oa['notify_url'] ?: kg_full_url(['for' => 'home.wechat.oa.notify_callback']); + + return $oa; + } + public function getVipSettings() { $vipRepo = new VipRepo(); @@ -144,7 +154,9 @@ class Setting extends Service public function updateSmsSettings($section, $settings) { - $settings['template'] = kg_json_encode($settings['template']); + if (isset($settings['template'])) { + $settings['template'] = kg_json_encode($settings['template']); + } $this->updateSettings($section, $settings); } @@ -160,4 +172,15 @@ class Setting extends Service } } + public function updateWechatSettings($section, $settings) + { + if ($section == 'wechat.oa') { + if (isset($settings['notice_template'])) { + $settings['notice_template'] = kg_json_encode($settings['notice_template']); + } + } + + $this->updateSettings($section, $settings); + } + } diff --git a/app/Http/Admin/Views/order/list.volt b/app/Http/Admin/Views/order/list.volt index fb154b05..8b6a39b3 100644 --- a/app/Http/Admin/Views/order/list.volt +++ b/app/Http/Admin/Views/order/list.volt @@ -36,7 +36,7 @@ {% set show_url = url({'for':'admin.order.show','id':item.id}) %} -

商品:{{ item.subject }} {{ item_type(item.item_type) }}

+

商品:{{ item.subject }}

单号:{{ item.sn }}

diff --git a/app/Http/Admin/Views/setting/sms.volt b/app/Http/Admin/Views/setting/sms.volt index dbddfc49..8601b0a2 100644 --- a/app/Http/Admin/Views/setting/sms.volt +++ b/app/Http/Admin/Views/setting/sms.volt @@ -53,21 +53,27 @@ 订单通知 - - - 复制 + + + 复制 退款通知 - - - 复制 + + + 复制 - 直播通知 - - - 复制 + 直播提醒 + + + 复制 + + + 回复通知 + + + 复制 diff --git a/app/Http/Admin/Views/setting/wechat.volt b/app/Http/Admin/Views/setting/wechat.volt new file mode 100644 index 00000000..a37d7091 --- /dev/null +++ b/app/Http/Admin/Views/setting/wechat.volt @@ -0,0 +1,16 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+ +
+
+ {{ partial('setting/wechat_oa') }} +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/setting/wechat_oa.volt b/app/Http/Admin/Views/setting/wechat_oa.volt new file mode 100644 index 00000000..efc70be5 --- /dev/null +++ b/app/Http/Admin/Views/setting/wechat_oa.volt @@ -0,0 +1,104 @@ +{% set notice_template = oa.notice_template|json_decode %} + +
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + + +
+
+
+
+ 模板配置 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称模板编号
登录提醒
订单通知
退款通知
直播提醒
回复通知
+
+
+ +
+ + + +
+
+
\ No newline at end of file diff --git a/app/Http/Api/Services/Account.php b/app/Http/Api/Services/Account.php index baf537f8..9ce43137 100644 --- a/app/Http/Api/Services/Account.php +++ b/app/Http/Api/Services/Account.php @@ -2,9 +2,11 @@ namespace App\Http\Api\Services; +use App\Models\User as UserModel; use App\Repos\User as UserRepo; use App\Services\Auth\Api as AuthService; use App\Services\Logic\Account\Register as RegisterService; +use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService; use App\Validators\Account as AccountValidator; class Account extends Service @@ -50,6 +52,8 @@ class Account extends Service $user = $validator->checkUserLogin($post['account'], $post['password']); + $this->handleLoginNotice($user); + return $this->auth->saveAuthInfo($user); } @@ -70,6 +74,8 @@ class Account extends Service $user = $validator->checkVerifyLogin($post['account'], $post['verify_code']); + $this->handleLoginNotice($user); + return $this->auth->saveAuthInfo($user); } @@ -78,4 +84,11 @@ class Account extends Service $this->auth->clearAuthInfo(); } + protected function handleLoginNotice(UserModel $user) + { + $service = new AccountLoginNoticeService(); + + $service->createTask($user); + } + } diff --git a/app/Http/Home/Controllers/UserConsoleController.php b/app/Http/Home/Controllers/UserConsoleController.php index 2b56c475..12ed2de5 100644 --- a/app/Http/Home/Controllers/UserConsoleController.php +++ b/app/Http/Home/Controllers/UserConsoleController.php @@ -2,6 +2,7 @@ namespace App\Http\Home\Controllers; +use App\Repos\WechatSubscribe as WechatSubscribeRepo; use App\Services\Logic\Account\OAuthProvider as OAuthProviderService; use App\Services\Logic\User\Console\AccountInfo as AccountInfoService; use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService; @@ -36,6 +37,15 @@ class UserConsoleController extends Controller return true; } + public function initialize() + { + parent::initialize(); + + $wechatOA = $this->getSettings('wechat.oa'); + + $this->view->setVar('wechat_oa', $wechatOA); + } + /** * @Get("/", name="home.uc.index") */ @@ -201,6 +211,25 @@ class UserConsoleController extends Controller $this->view->setVar('pager', $pager); } + /** + * @Get("/subscribe", name="home.uc.subscribe") + */ + public function subscribeAction() + { + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($this->authUser->id); + + $subscribed = 0; + + if ($subscribe) { + $subscribed = $subscribe->deleted == 0 ? 1 : 0; + } + + $this->view->pick('user/console/subscribe'); + $this->view->setVar('subscribed', $subscribed); + } + /** * @Post("/profile/update", name="home.uc.update_profile") */ diff --git a/app/Http/Home/Controllers/WechatOfficialAccountController.php b/app/Http/Home/Controllers/WechatOfficialAccountController.php new file mode 100644 index 00000000..223170e7 --- /dev/null +++ b/app/Http/Home/Controllers/WechatOfficialAccountController.php @@ -0,0 +1,76 @@ +getSubscribeStatus(); + + return $this->jsonSuccess(['status' => $status]); + } + + /** + * @Get("/subscribe/qrcode", name="home.wechat.oa.sub_qrcode") + */ + public function subscribeQrCodeAction() + { + $service = new WechatOAService(); + + $qrcode = $service->createSubscribeQrCode(); + + return $this->jsonSuccess(['qrcode' => $qrcode]); + } + + /** + * @Get("/notify", name="home.wechat.oa.verify") + */ + public function verifyAction() + { + $service = new WechatOAService(); + + $app = $service->getOfficialAccount(); + + $response = $app->server->serve(); + + $response->send(); + + exit; + } + + /** + * @Post("/notify", name="home.wechat.oa.notify") + */ + public function notifyAction() + { + $service = new WechatOAService(); + + $app = $service->getOfficialAccount(); + + $app->server->push(function ($message) use ($service) { + return $service->handleNotify($message); + }); + + $response = $app->server->serve(); + + $response->send(); + + exit; + } + +} diff --git a/app/Http/Home/Services/Account.php b/app/Http/Home/Services/Account.php index 38efb3b2..7f7fb06f 100644 --- a/app/Http/Home/Services/Account.php +++ b/app/Http/Home/Services/Account.php @@ -2,9 +2,11 @@ namespace App\Http\Home\Services; +use App\Models\User as UserModel; use App\Repos\User as UserRepo; use App\Services\Auth\Home as AuthService; use App\Services\Logic\Account\Register as RegisterService; +use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService; use App\Validators\Account as AccountValidator; use App\Validators\Captcha as CaptchaValidator; @@ -48,6 +50,8 @@ class Account extends Service $validator->checkCode($post['ticket'], $post['rand']); + $this->handleLoginNotice($user); + $this->auth->saveAuthInfo($user); } @@ -59,6 +63,8 @@ class Account extends Service $user = $validator->checkVerifyLogin($post['account'], $post['verify_code']); + $this->handleLoginNotice($user); + $this->auth->saveAuthInfo($user); } @@ -67,4 +73,11 @@ class Account extends Service $this->auth->clearAuthInfo(); } + protected function handleLoginNotice(UserModel $user) + { + $service = new AccountLoginNoticeService(); + + $service->createTask($user); + } + } diff --git a/app/Http/Home/Services/Connect.php b/app/Http/Home/Services/Connect.php index 0cef8358..2e3a26f5 100644 --- a/app/Http/Home/Services/Connect.php +++ b/app/Http/Home/Services/Connect.php @@ -8,6 +8,7 @@ use App\Repos\Connect as ConnectRepo; use App\Repos\User as UserRepo; use App\Services\Auth\Home as AuthService; use App\Services\Logic\Account\Register as RegisterService; +use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService; use App\Services\OAuth\QQ as QQAuth; use App\Services\OAuth\WeiBo as WeiBoAuth; use App\Services\OAuth\WeiXin as WeiXinAuth; @@ -32,6 +33,8 @@ class Connect extends Service $this->handleConnectRelation($user, $openUser); + $this->handleLoginNotice($user); + $auth = $this->getAppAuth(); $auth->saveAuthInfo($user); @@ -57,6 +60,8 @@ class Connect extends Service $this->handleConnectRelation($user, $openUser); + $this->handleLoginNotice($user); + $auth = $this->getAppAuth(); $auth->saveAuthInfo($user); @@ -75,6 +80,8 @@ class Connect extends Service $user = $userRepo->findById($connect->user_id); + $this->handleLoginNotice($user); + $auth = $this->getAppAuth(); $auth->saveAuthInfo($user); @@ -208,4 +215,11 @@ class Connect extends Service } } + protected function handleLoginNotice(UserModel $user) + { + $service = new AccountLoginNoticeService(); + + $service->createTask($user); + } + } diff --git a/app/Http/Home/Services/WechatOfficialAccount.php b/app/Http/Home/Services/WechatOfficialAccount.php new file mode 100644 index 00000000..ea19fd02 --- /dev/null +++ b/app/Http/Home/Services/WechatOfficialAccount.php @@ -0,0 +1,238 @@ +getOfficialAccount(); + } + + public function createSubscribeQrCode() + { + $user = $this->getLoginUser(); + + $app = $this->getOfficialAccount(); + + $result = $app->qrcode->temporary($user->id); + + return $app->qrcode->url($result['ticket']); + } + + public function getSubscribeStatus() + { + $user = $this->getLoginUser(); + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($user->id); + + $status = 0; + + if ($subscribe) { + $status = $subscribe->deleted == 0 ? 1 : 0; + } + + return $status; + } + + public function handleNotify($message) + { + $service = new WechatService(); + + $service->logger->debug('Received Message ' . json_encode($message)); + + switch ($message['MsgType']) { + case 'event': + switch ($message['Event']) { + case 'subscribe': + return $this->handleSubscribeEvent($message); + break; + case 'unsubscribe': + return $this->handleUnsubscribeEvent($message); + break; + case 'SCAN': + return $this->handleScanEvent($message); + break; + case 'CLICK': + return $this->handleClickEvent($message); + break; + case 'VIEW': + return $this->handleViewEvent($message); + break; + case 'LOCATION': + return $this->handleLocationEvent($message); + break; + default: + return $this->emptyReplyMessage(); + break; + } + break; + case 'text': + return $this->handleTextReply($message); + break; + case 'image': + return $this->handleImageReply($message); + break; + case 'voice': + return $this->handleVoiceReply($message); + break; + case 'video': + return $this->handleVideoReply($message); + break; + case 'shortvideo': + return $this->handleShortVideoReply($message); + break; + case 'location': + return $this->handleLocationReply($message); + break; + case 'link': + return $this->handleLinkReply($message); + break; + default: + return $this->emptyReplyMessage(); + break; + } + } + + protected function handleSubscribeEvent($message) + { + $openId = $message['FromUserName'] ?? ''; + $eventKey = $message['EventKey'] ?? ''; + + if (!$eventKey) { + return $this->emptyReplyMessage(); + } + + $userId = str_replace('qrscene_', '', $eventKey); + + $this->handleSubscribeRelation($userId, $openId); + + return new TextMessage('开心呀,我们又多了一个小伙伴!'); + } + + protected function handleUnsubscribeEvent($message) + { + $openId = $message['FromUserName'] ?? ''; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByOpenId($openId); + + if ($subscribe) { + $subscribe->deleted = 1; + $subscribe->update(); + } + + return new TextMessage('伤心呀,我们又少了一个小伙伴!'); + } + + protected function handleScanEvent($message) + { + /** + * 注意:当已关注过用户扫码时,"EventKey"没有带"qrscene_"前缀 + */ + $openId = $message['FromUserName'] ?? ''; + $eventKey = $message['EventKey'] ?? ''; + $userId = $eventKey; + + $this->handleSubscribeRelation($userId, $openId); + } + + protected function handleClickEvent($message) + { + $this->defaultReplyMessage(); + } + + protected function handleViewEvent($message) + { + $this->defaultReplyMessage(); + } + + protected function handleLocationEvent($message) + { + $this->defaultReplyMessage(); + } + + protected function handleTextReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleImageReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleVoiceReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleVideoReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleShortVideoReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleLocationReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function handleLinkReply($message) + { + return $this->defaultReplyMessage(); + } + + protected function emptyReplyMessage() + { + return new TextMessage(''); + } + + protected function defaultReplyMessage() + { + return new TextMessage('没有匹配的服务,如有需要请联系客服!'); + } + + protected function handleSubscribeRelation($userId, $openId) + { + $validator = new UserValidator(); + + $validator->checkUser($userId); + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByOpenId($openId); + + if ($subscribe) { + if ($subscribe->deleted == 1) { + $subscribe->deleted = 0; + $subscribe->update(); + } + } else { + $subscribe = $subscribeRepo->findSubscribe($userId, $openId); + if (!$subscribe) { + $subscribe = new WechatSubscribeModel(); + $subscribe->user_id = $userId; + $subscribe->open_id = $openId; + $subscribe->create(); + } + } + } + +} diff --git a/app/Http/Home/Views/user/console/menu.volt b/app/Http/Home/Views/user/console/menu.volt index cc8ed6ac..e670e951 100644 --- a/app/Http/Home/Views/user/console/menu.volt +++ b/app/Http/Home/Views/user/console/menu.volt @@ -52,6 +52,9 @@ \ No newline at end of file diff --git a/app/Http/Home/Views/user/console/subscribe.volt b/app/Http/Home/Views/user/console/subscribe.volt new file mode 100644 index 00000000..1fe6a2c7 --- /dev/null +++ b/app/Http/Home/Views/user/console/subscribe.volt @@ -0,0 +1,33 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
+
{{ partial('user/console/menu') }}
+
+
+
+ 关注订阅 +
+
+ {% if subscribed == 0 %} +
+
订阅官方公众号,接收重要通知!
+ {% else %} +
你已经订阅官方公众号
+ {% endif %} +
+
+ +
+
+
+
+ +{% endblock %} + +{% block include_js %} + + {{ js_include('home/js/user.console.subscribe.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Library/AppInfo.php b/app/Library/AppInfo.php index e0ab2ed3..44cf5878 100644 --- a/app/Library/AppInfo.php +++ b/app/Library/AppInfo.php @@ -11,7 +11,7 @@ class AppInfo protected $link = 'https://gitee.com/koogua'; - protected $version = '1.2.1'; + protected $version = '1.2.2'; public function __get($name) { diff --git a/app/Models/Consult.php b/app/Models/Consult.php index a790c650..31ea904d 100644 --- a/app/Models/Consult.php +++ b/app/Models/Consult.php @@ -36,12 +36,19 @@ class Consult extends Model public $chapter_id; /** - * 用户编号 + * 提主编号 * * @var int */ public $owner_id; + /** + * 答主编号 + * + * @var int + */ + public $replier_id; + /** * 提问 * diff --git a/app/Models/Role.php b/app/Models/Role.php index 827a368a..a30b573b 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -105,6 +105,8 @@ class Role extends Model { if (is_array($this->routes) && !empty($this->routes)) { $this->routes = kg_json_encode($this->routes); + } else { + $this->routes = ''; } $this->create_time = time(); diff --git a/app/Models/Task.php b/app/Models/Task.php index 45890752..dd85bb59 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -11,6 +11,12 @@ class Task extends Model const TYPE_DELIVER = 1; // 发货 const TYPE_REFUND = 2; // 退款 + const TYPE_NOTICE_ACCOUNT_LOGIN = 11; // 帐号登录通知 + const TYPE_NOTICE_LIVE_BEGIN = 12; // 直播开始通知 + const TYPE_NOTICE_ORDER_FINISH = 13; // 订单完成通知 + const TYPE_NOTICE_REFUND_FINISH = 14; // 退款完成通知 + const TYPE_NOTICE_CONSULT_REPLY = 15; // 咨询回复通知 + /** * 优先级 */ diff --git a/app/Models/WechatSubscribe.php b/app/Models/WechatSubscribe.php new file mode 100644 index 00000000..e1633caa --- /dev/null +++ b/app/Models/WechatSubscribe.php @@ -0,0 +1,79 @@ +addBehavior( + new SoftDelete([ + 'field' => 'deleted', + 'value' => 1, + ]) + ); + } + + public function beforeCreate() + { + $this->create_time = time(); + } + + public function beforeUpdate() + { + $this->update_time = time(); + } + +} diff --git a/app/Repos/CourseUser.php b/app/Repos/CourseUser.php index 12704ac5..01ece420 100644 --- a/app/Repos/CourseUser.php +++ b/app/Repos/CourseUser.php @@ -146,4 +146,16 @@ class CourseUser extends Repository ->execute(); } + /** + * @param int $courseId + * @return ResultsetInterface|Resultset|CourseUserModel[] + */ + public function findByCourseId($courseId) + { + return CourseUserModel::query() + ->where('course_id = :course_id:', ['course_id' => $courseId]) + ->andWhere('deleted = 0') + ->execute(); + } + } diff --git a/app/Repos/WechatSubscribe.php b/app/Repos/WechatSubscribe.php new file mode 100644 index 00000000..2043d889 --- /dev/null +++ b/app/Repos/WechatSubscribe.php @@ -0,0 +1,57 @@ + 'user_id= ?1 AND open_id = ?2', + 'bind' => [1 => $userId, 2 => $openId], + ]); + } + + /** + * @param int $id + * @return WechatSubscribeModel|Model|bool + */ + public function findById($id) + { + return WechatSubscribeModel::findFirst($id); + } + + /** + * @param int $userId + * @return WechatSubscribeModel|Model|bool + */ + public function findByUserId($userId) + { + return WechatSubscribeModel::findFirst([ + 'conditions' => 'user_id = :user_id:', + 'bind' => ['user_id' => $userId], + ]); + } + + /** + * @param string $openId + * @return WechatSubscribeModel|Model|bool + */ + public function findByOpenId($openId) + { + return WechatSubscribeModel::findFirst([ + 'conditions' => 'open_id = :open_id:', + 'bind' => ['open_id' => $openId], + ]); + } + +} diff --git a/app/Services/Auth/Mobile.php b/app/Services/Auth/Mobile.php deleted file mode 100644 index 5f94bcb4..00000000 --- a/app/Services/Auth/Mobile.php +++ /dev/null @@ -1,44 +0,0 @@ -getAuthKey(); - - $authInfo = [ - 'id' => $user->id, - 'name' => $user->name, - ]; - - $this->session->set($authKey, $authInfo); - } - - public function clearAuthInfo() - { - $authKey = $this->getAuthKey(); - - $this->session->remove($authKey); - } - - public function getAuthInfo() - { - $authKey = $this->getAuthKey(); - - $authInfo = $this->session->get($authKey); - - return $authInfo ?: null; - } - - public function getAuthKey() - { - return 'mobile_auth_info'; - } - -} diff --git a/app/Services/ChapterVod.php b/app/Services/ChapterVod.php index fe711dc6..89d6d292 100644 --- a/app/Services/ChapterVod.php +++ b/app/Services/ChapterVod.php @@ -41,9 +41,12 @@ class ChapterVod extends Service $vodTemplates = $this->getVodTemplates(); + /** + * 腾讯云播放器只支持[od|hd|sd],遇到fd替换为od + */ foreach ($vodTemplates as $key => $template) { if ($height >= $template['height']) { - return $key; + return $key == 'fd' ? $default : $key; } } diff --git a/app/Services/LiveNotify.php b/app/Services/LiveNotify.php index ef1fd11d..edf7982d 100644 --- a/app/Services/LiveNotify.php +++ b/app/Services/LiveNotify.php @@ -5,6 +5,8 @@ namespace App\Services; use App\Models\Chapter as ChapterModel; use App\Models\ChapterLive as ChapterLiveModel; use App\Repos\Chapter as ChapterRepo; +use App\Repos\CourseUser as CourseUserRepo; +use App\Services\Logic\Notice\LiveBegin as LiveBeginNotice; class LiveNotify extends Service { @@ -42,16 +44,6 @@ class LiveNotify extends Service return $result; } - public function getNotifyKey() - { - return 'live_notify'; - } - - public function getSentNotifyKey() - { - return 'live_notify_sent'; - } - /** * 推流 */ @@ -73,7 +65,7 @@ class LiveNotify extends Service $chapterLive->update(['status' => ChapterLiveModel::STATUS_ACTIVE]); - $this->sendBeginNotify($chapter); + $this->handleStreamBeginNotice($chapter); return true; } @@ -126,15 +118,21 @@ class LiveNotify extends Service } - protected function sendBeginNotify(ChapterModel $chapter) + protected function handleStreamBeginNotice(ChapterModel $chapter) { - $redis = $this->getRedis(); + $courseUserRepo = new CourseUserRepo(); - $key = $this->getNotifyKey(); + $courseUsers = $courseUserRepo->findByCourseId($chapter->course_id); - $redis->sAdd($key, $chapter->id); + if ($courseUsers->count() == 0) { + return; + } - $redis->expire($key, 86400); + $notice = new LiveBeginNotice(); + + foreach ($courseUsers as $courseUser) { + $notice->createTask($chapter, $courseUser); + } } protected function getChapter($streamName) diff --git a/app/Services/Logic/Consult/ConsultInfo.php b/app/Services/Logic/Consult/ConsultInfo.php index 52dcf335..f8b5590b 100644 --- a/app/Services/Logic/Consult/ConsultInfo.php +++ b/app/Services/Logic/Consult/ConsultInfo.php @@ -37,6 +37,7 @@ class ConsultInfo extends Service $result['course'] = $this->handleCourseInfo($consult); $result['chapter'] = $this->handleChapterInfo($consult); $result['owner'] = $this->handleOwnerInfo($consult); + $result['replier'] = $this->handleReplierInfo($consult); return $result; } @@ -85,4 +86,19 @@ class ConsultInfo extends Service ]; } + protected function handleReplierInfo(ConsultModel $consult) + { + $userRepo = new UserRepo(); + + $replier = $userRepo->findById($consult->replier_id); + + if (!$replier) return new \stdClass(); + + return [ + 'id' => $replier->id, + 'name' => $replier->name, + 'avatar' => $replier->avatar, + ]; + } + } diff --git a/app/Services/Logic/Consult/ConsultReply.php b/app/Services/Logic/Consult/ConsultReply.php index 1b0e457a..25e88371 100644 --- a/app/Services/Logic/Consult/ConsultReply.php +++ b/app/Services/Logic/Consult/ConsultReply.php @@ -2,11 +2,13 @@ namespace App\Services\Logic\Consult; +use App\Models\Consult as ConsultModel; use App\Services\Logic\ConsultTrait; -use App\Services\Logic\Service; +use App\Services\Logic\Notice\ConsultReply as ConsultReplyNotice; +use App\Services\Logic\Service as LogicService; use App\Validators\Consult as ConsultValidator; -class ConsultReply extends Service +class ConsultReply extends LogicService { use ConsultTrait; @@ -25,12 +27,29 @@ class ConsultReply extends Service $answer = $validator->checkAnswer($post['answer']); - $consult->update([ - 'answer' => $answer, - 'reply_time' => time(), - ]); + $firstReply = false; + + if ($consult->reply_time == 0) { + $firstReply = true; + } + + $consult->replier_id = $user->id; + $consult->reply_time = time(); + $consult->answer = $answer; + $consult->update(); + + if ($firstReply) { + $this->handleReplyNotice($consult); + } return $consult; } + protected function handleReplyNotice(ConsultModel $consult) + { + $notice = new ConsultReplyNotice(); + + $notice->createTask($consult); + } + } diff --git a/app/Services/Logic/Notice/AccountLogin.php b/app/Services/Logic/Notice/AccountLogin.php new file mode 100644 index 00000000..6ae4b18f --- /dev/null +++ b/app/Services/Logic/Notice/AccountLogin.php @@ -0,0 +1,61 @@ +item_info; + + $userId = $task->item_info['user']['id']; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($userId); + + if ($subscribe && $subscribe->deleted == 0) { + + $notice = new WechatAccountLoginNotice(); + + return $notice->handle($subscribe, $params); + } + } + + public function createTask(UserModel $user) + { + $task = new TaskModel(); + + $loginIp = $this->getClientIp(); + $loginRegion = kg_ip2region($loginIp); + + $itemInfo = [ + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + 'login_ip' => $loginIp, + 'login_region' => $loginRegion, + 'login_time' => time(), + ]; + + $task->item_id = $user->id; + $task->item_info = $itemInfo; + $task->item_type = TaskModel::TYPE_NOTICE_ACCOUNT_LOGIN; + $task->priority = TaskModel::PRIORITY_LOW; + $task->status = TaskModel::STATUS_PENDING; + + $task->create(); + } + +} diff --git a/app/Services/Logic/Notice/ConsultReply.php b/app/Services/Logic/Notice/ConsultReply.php new file mode 100644 index 00000000..cdd3c34a --- /dev/null +++ b/app/Services/Logic/Notice/ConsultReply.php @@ -0,0 +1,91 @@ +item_info['consult']['id']; + + $consultRepo = new ConsultRepo(); + + $consult = $consultRepo->findById($consultId); + + $courseRepo = new CourseRepo(); + + $course = $courseRepo->findById($consult->course_id); + + $userRepo = new UserRepo(); + + $user = $userRepo->findById($consult->owner_id); + + $replier = $userRepo->findById($consult->replier_id); + + $params = [ + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + 'replier' => [ + 'id' => $replier->id, + 'name' => $replier->name, + ], + 'consult' => [ + 'id' => $consult->id, + 'question' => $consult->question, + 'answer' => $consult->answer, + ], + 'course' => [ + 'id' => $course->id, + 'title' => $course->title, + ], + ]; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($consult->owner_id); + + if ($subscribe && $subscribe->deleted == 0) { + + $notice = new WechatConsultReplyNotice(); + + return $notice->handle($subscribe, $params); + + } else { + + $notice = new SmsConsultReplyNotice(); + + return $notice->handle($user, $params); + } + } + + public function createTask(ConsultModel $consult) + { + $task = new TaskModel(); + + $itemInfo = [ + 'consult' => ['id' => $consult->id], + ]; + + $task->item_id = $consult->id; + $task->item_info = $itemInfo; + $task->item_type = TaskModel::TYPE_NOTICE_CONSULT_REPLY; + $task->priority = TaskModel::PRIORITY_LOW; + $task->status = TaskModel::STATUS_PENDING; + + $task->create(); + } + +} diff --git a/app/Services/Logic/Notice/LiveBegin.php b/app/Services/Logic/Notice/LiveBegin.php new file mode 100644 index 00000000..f125c56c --- /dev/null +++ b/app/Services/Logic/Notice/LiveBegin.php @@ -0,0 +1,99 @@ +item_info['course_user']; + $chapterId = $task->item_info['chapter']['id']; + + $courseRepo = new CourseRepo(); + + $course = $courseRepo->findById($courseUser['course_id']); + + $userRepo = new UserRepo(); + + $user = $userRepo->findById($courseUser['user_id']); + + $chapterRepo = new ChapterRepo(); + + $chapter = $chapterRepo->findById($chapterId); + + $params = [ + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + 'course' => [ + 'id' => $course->id, + 'title' => $course->title, + ], + 'chapter' => [ + 'id' => $chapter->id, + 'title' => $chapter->title, + ], + 'live' => [ + 'start_time' => $chapter->attrs['start_time'], + 'end_time' => $chapter->attrs['end_time'], + ], + 'course_user' => $courseUser, + ]; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($user->id); + + if ($subscribe && $subscribe->deleted == 0) { + + $notice = new WechatLiveBeginNotice(); + + return $notice->handle($subscribe, $params); + + } else { + + $notice = new SmsLiveBeginNotice(); + + return $notice->handle($user, $params); + } + } + + public function createTask(ChapterModel $chapter, CourseUserModel $courseUser) + { + $task = new TaskModel(); + + $itemInfo = [ + 'course_user' => [ + 'course_id' => $courseUser->course_id, + 'user_id' => $courseUser->user_id, + 'role_type' => $courseUser->role_type, + 'source_type' => $courseUser->role_type, + ], + 'chapter' => [ + 'id' => $chapter->id, + ], + ]; + + $task->item_id = $chapter->id; + $task->item_info = $itemInfo; + $task->item_type = TaskModel::TYPE_NOTICE_LIVE_BEGIN; + $task->priority = TaskModel::PRIORITY_LOW; + $task->status = TaskModel::STATUS_PENDING; + + $task->create(); + } + +} diff --git a/app/Services/Logic/Notice/OrderFinish.php b/app/Services/Logic/Notice/OrderFinish.php new file mode 100644 index 00000000..5b70e132 --- /dev/null +++ b/app/Services/Logic/Notice/OrderFinish.php @@ -0,0 +1,76 @@ +item_info['order']['id']; + + $orderRepo = new OrderRepo(); + + $order = $orderRepo->findById($orderId); + + $userRepo = new UserRepo(); + + $user = $userRepo->findById($order->owner_id); + + $params = [ + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + 'order' => [ + 'sn' => $order->sn, + 'subject' => $order->subject, + 'amount' => $order->amount, + ], + ]; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($order->owner_id); + + if ($subscribe && $subscribe->deleted == 0) { + + $notice = new WechatOrderFinishNotice(); + + return $notice->handle($subscribe, $params); + + } else { + + $notice = new SmsOrderFinishNotice(); + + return $notice->handle($user, $params); + } + } + + public function createTask(OrderModel $order) + { + $task = new TaskModel(); + + $itemInfo = [ + 'order' => ['id' => $order->id], + ]; + + $task->item_id = $order->id; + $task->item_info = $itemInfo; + $task->item_type = TaskModel::TYPE_NOTICE_ORDER_FINISH; + $task->priority = TaskModel::PRIORITY_HIGH; + $task->status = TaskModel::STATUS_PENDING; + + $task->create(); + } + +} diff --git a/app/Services/Logic/Notice/RefundFinish.php b/app/Services/Logic/Notice/RefundFinish.php new file mode 100644 index 00000000..468f5539 --- /dev/null +++ b/app/Services/Logic/Notice/RefundFinish.php @@ -0,0 +1,76 @@ +item_info['refund']['id']; + + $refundRepo = new RefundRepo(); + + $refund = $refundRepo->findById($refundId); + + $userRepo = new UserRepo(); + + $user = $userRepo->findById($refund->owner_id); + + $params = [ + 'user' => [ + 'id' => $user->id, + 'name' => $user->name, + ], + 'order' => [ + 'sn' => $refund->sn, + 'subject' => $refund->subject, + 'amount' => $refund->amount, + ], + ]; + + $subscribeRepo = new WechatSubscribeRepo(); + + $subscribe = $subscribeRepo->findByUserId($refund->owner_id); + + if ($subscribe && $subscribe->deleted == 0) { + + $notice = new WechatRefundFinishNotice(); + + return $notice->handle($subscribe, $params); + + } else { + + $notice = new SmsRefundFinishNotice(); + + return $notice->handle($user, $params); + } + } + + public function createTask(RefundModel $refund) + { + $task = new TaskModel(); + + $itemInfo = [ + 'refund' => ['id' => $refund->id], + ]; + + $task->item_id = $refund->id; + $task->item_info = $itemInfo; + $task->item_type = TaskModel::TYPE_NOTICE_ORDER_FINISH; + $task->priority = TaskModel::PRIORITY_MIDDLE; + $task->status = TaskModel::STATUS_PENDING; + + $task->create(); + } + +} diff --git a/app/Services/Sms/Live.php b/app/Services/Sms/Live.php deleted file mode 100644 index 356f40f1..00000000 --- a/app/Services/Sms/Live.php +++ /dev/null @@ -1,50 +0,0 @@ -findById($userId); - - if (empty($account->phone)) { - return false; - } - - $chapterRepo = new ChapterRepo(); - - $chapter = $chapterRepo->findById($chapterId); - - $courseRepo = new CourseRepo(); - - $course = $courseRepo->findById($chapter->course_id); - - $params = [ - $course->title, - $chapter->title, - $startTime, - ]; - - $templateId = $this->getTemplateId($this->templateCode); - - return $this->send($account->phone, $templateId, $params); - } - -} diff --git a/app/Services/Sms/Notice/ConsultReply.php b/app/Services/Sms/Notice/ConsultReply.php new file mode 100644 index 00000000..85d21f21 --- /dev/null +++ b/app/Services/Sms/Notice/ConsultReply.php @@ -0,0 +1,37 @@ +findById($user->id); + + if (!$account->phone) return null; + + $templateId = $this->getTemplateId($this->templateCode); + + $params = [ + $params['replier']['name'], + $params['course']['title'], + ]; + + return $this->send($account->phone, $templateId, $params); + } + +} diff --git a/app/Services/Sms/Notice/LiveBegin.php b/app/Services/Sms/Notice/LiveBegin.php new file mode 100644 index 00000000..104b8f86 --- /dev/null +++ b/app/Services/Sms/Notice/LiveBegin.php @@ -0,0 +1,38 @@ +findById($user->id); + + if (!$account->phone) return null; + + $params = [ + $params['course']['title'], + $params['chapter']['title'], + date('H:i', $params['live']['start_time']), + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($account->phone, $templateId, $params); + } + +} diff --git a/app/Services/Sms/Notice/OrderFinish.php b/app/Services/Sms/Notice/OrderFinish.php new file mode 100644 index 00000000..e67fd2a0 --- /dev/null +++ b/app/Services/Sms/Notice/OrderFinish.php @@ -0,0 +1,38 @@ +findById($user->id); + + if (!$account->phone) return null; + + $templateId = $this->getTemplateId($this->templateCode); + + $params = [ + $params['order']['subject'], + $params['order']['sn'], + $params['order']['amount'], + ]; + + return $this->send($account->phone, $templateId, $params); + } + +} diff --git a/app/Services/Sms/Notice/RefundFinish.php b/app/Services/Sms/Notice/RefundFinish.php new file mode 100644 index 00000000..f64d8908 --- /dev/null +++ b/app/Services/Sms/Notice/RefundFinish.php @@ -0,0 +1,38 @@ +findById($user->id); + + if (!$account->phone) return null; + + $templateId = $this->getTemplateId($this->templateCode); + + $params = [ + $params['refund']['subject'], + $params['refund']['sn'], + $params['refund']['amount'], + ]; + + return $this->send($account->phone, $templateId, $params); + } + +} diff --git a/app/Services/Sms/Order.php b/app/Services/Sms/Order.php deleted file mode 100644 index 64e4a6df..00000000 --- a/app/Services/Sms/Order.php +++ /dev/null @@ -1,39 +0,0 @@ -findById($order->owner_id); - - if (empty($account->phone)) { - return false; - } - - $templateId = $this->getTemplateId($this->templateCode); - - $params = [ - $order->subject, - $order->sn, - $order->amount, - ]; - - return $this->send($account->phone, $templateId, $params); - } - -} diff --git a/app/Services/Sms/Refund.php b/app/Services/Sms/Refund.php deleted file mode 100644 index 5796f82b..00000000 --- a/app/Services/Sms/Refund.php +++ /dev/null @@ -1,39 +0,0 @@ -findById($refund->owner_id); - - if (empty($account->phone)) { - return false; - } - - $templateId = $this->getTemplateId($this->templateCode); - - $params = [ - $refund->subject, - $refund->sn, - $refund->amount, - ]; - - return $this->send($account->phone, $templateId, $params); - } - -} diff --git a/app/Services/Wechat.php b/app/Services/Wechat.php new file mode 100644 index 00000000..ed106111 --- /dev/null +++ b/app/Services/Wechat.php @@ -0,0 +1,59 @@ +logger = $this->getLogger('wechat'); + } + + public function getOfficialAccount() + { + $settings = $this->getSettings('wechat.oa'); + + $config = [ + 'app_id' => $settings['app_id'], + 'secret' => $settings['app_secret'], + 'token' => $settings['app_token'], + 'aes_key' => $settings['aes_key'], + 'log' => $this->getLogOptions(), + ]; + + return Factory::officialAccount($config); + } + + protected function getLogOptions() + { + $config = $this->getConfig(); + + $default = $config->get('env') == ENV_DEV ? 'dev' : 'prod'; + + return [ + 'default' => $default, + 'channels' => [ + 'dev' => [ + 'driver' => 'daily', + 'path' => log_path('wechat.log'), + 'level' => 'debug', + ], + 'prod' => [ + 'driver' => 'daily', + 'path' => log_path('wechat.log'), + 'level' => 'info', + ], + ] + ]; + } + +} diff --git a/app/Services/Wechat/Notice/AccountLogin.php b/app/Services/Wechat/Notice/AccountLogin.php new file mode 100644 index 00000000..b4e901e7 --- /dev/null +++ b/app/Services/Wechat/Notice/AccountLogin.php @@ -0,0 +1,43 @@ + $first, + 'remark' => $remark, + 'keyword1' => $loginRegion, + 'keyword2' => $loginTime, + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($subscribe->open_id, $templateId, $params); + } + +} diff --git a/app/Services/Wechat/Notice/ConsultReply.php b/app/Services/Wechat/Notice/ConsultReply.php new file mode 100644 index 00000000..f33db7f9 --- /dev/null +++ b/app/Services/Wechat/Notice/ConsultReply.php @@ -0,0 +1,35 @@ + $first, + 'remark' => $remark, + 'keyword1' => $params['course']['title'], + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($subscribe->open_id, $templateId, $params); + } + +} diff --git a/app/Services/Wechat/Notice/LiveBegin.php b/app/Services/Wechat/Notice/LiveBegin.php new file mode 100644 index 00000000..83077f4d --- /dev/null +++ b/app/Services/Wechat/Notice/LiveBegin.php @@ -0,0 +1,36 @@ + $first, + 'remark' => $remark, + 'keyword1' => $params['course']['title'], + 'keyword2' => $params['chapter']['title'], + 'keyword3' => date('Y-m-d H:i', $params['live']['start_time']), + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($subscribe->open_id, $templateId, $params); + } + +} diff --git a/app/Services/Wechat/Notice/OrderFinish.php b/app/Services/Wechat/Notice/OrderFinish.php new file mode 100644 index 00000000..c21ea2a9 --- /dev/null +++ b/app/Services/Wechat/Notice/OrderFinish.php @@ -0,0 +1,37 @@ + $first, + 'remark' => $remark, + 'keyword1' => $params['order']['subject'], + 'keyword2' => $params['order']['sn'], + 'keyword3' => $params['order']['amount'], + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($subscribe->open_id, $templateId, $params); + } + +} diff --git a/app/Services/Wechat/Notice/RefundFinish.php b/app/Services/Wechat/Notice/RefundFinish.php new file mode 100644 index 00000000..6b2030fe --- /dev/null +++ b/app/Services/Wechat/Notice/RefundFinish.php @@ -0,0 +1,36 @@ + $first, + 'remark' => $remark, + 'keyword1' => $params['refund']['subject'], + 'keyword2' => $params['refund']['sn'], + 'keyword3' => $params['refund']['amount'], + ]; + + $templateId = $this->getTemplateId($this->templateCode); + + return $this->send($subscribe->open_id, $templateId, $params); + } + +} diff --git a/app/Services/WechatNotice.php b/app/Services/WechatNotice.php new file mode 100644 index 00000000..18c85ba4 --- /dev/null +++ b/app/Services/WechatNotice.php @@ -0,0 +1,103 @@ +settings = $this->getSettings('wechat.oa'); + + $this->logger = $this->getLogger('wechat'); + } + + /** + * 发送模板消息 + * + * @param string $openId + * @param string $templateId + * @param array $params + * @param string $url + * @param array $miniProgram + * @return bool + */ + public function send($openId, $templateId, $params, $url = null, $miniProgram = []) + { + $service = new WechatService(); + + $app = $service->getOfficialAccount(); + + $content = [ + 'touser' => $openId, + 'template_id' => $templateId, + 'data' => $this->formatParams($params), + ]; + + if ($url) { + $content['url'] = $url; + } + + if ($miniProgram) { + $content['miniprogram'] = $miniProgram; + } + + try { + + $this->logger->debug('Send Template Message Request ' . kg_json_encode($content)); + + $response = $app->template_message->send($content); + + $this->logger->debug('Send Template Message Response ' . kg_json_encode($response)); + + $result = $response['errcode'] == 0; + + if ($result == false) { + $this->logger->error('Send Template Message Failed ' . kg_json_encode($response)); + } + + } catch (\Exception $e) { + + $this->logger->error('Send Template Message Exception ' . kg_json_encode([ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + ])); + + $result = false; + } + + return $result; + } + + protected function formatParams($params) + { + if (!empty($params)) { + $params = array_map(function ($value) { + return strval($value); + }, $params); + } + + return $params; + } + + protected function getTemplateId($code) + { + $template = json_decode($this->settings['notice_template'], true); + + return $template[$code] ?? null; + } + +} diff --git a/composer.json b/composer.json index bcd0e1cc..fe283e37 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "aferrandini/phpqrcode": "1.0.1", "xiaochong0302/ip2region": "^1.0", "robmorgan/phinx": "^0.12", - "lcobucci/jwt": "^3.3" + "lcobucci/jwt": "^3.3", + "overtrue/wechat": "^4.2" }, "require-dev": { "odan/phinx-migrations-generator": "^5.1", diff --git a/composer.lock b/composer.lock index 02109891..d6cb71b6 100644 --- a/composer.lock +++ b/composer.lock @@ -1,20 +1,20 @@ { - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "cce345a8509cd31ff8492310a2a2332a", - "packages": [ - { - "name": "aferrandini/phpqrcode", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/aferrandini/PHPQRCode.git", - "reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46" - }, - "dist": { + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "60ff0e1868be7414a1b31d397ced7fbd", + "packages": [ + { + "name": "aferrandini/phpqrcode", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/aferrandini/PHPQRCode.git", + "reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46" + }, + "dist": { "type": "zip", "url": "https://api.github.com/repos/aferrandini/PHPQRCode/zipball/3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46", "reference": "3c1c0454d43710ab5bbe19a51ad4cb41c22e3d46", @@ -25,27 +25,27 @@ "preferred": true } ] - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "PHPQRCode": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ariel Ferrandini", - "email": "arielferrandini@gmail.com", - "homepage": "http://www.ferrandini.com/", - "role": "Developer" - } + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PHPQRCode": "lib/" + } + }, + "notification-url": "https://packagist.jp/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ariel Ferrandini", + "email": "arielferrandini@gmail.com", + "homepage": "http://www.ferrandini.com/", + "role": "Developer" + } ], "description": "PHPQRCode porting and changed for PHP 5.3 compatibility", "homepage": "https://github.com/aferrandini/PHPQRCode", @@ -344,26 +344,80 @@ ], "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "time": "2019-07-30T19:33:28+00:00" + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "time": "2019-07-30T19:33:28+00:00" }, + { + "name": "easywechat-composer/easywechat-composer", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/mingyoung/easywechat-composer.git", + "reference": "93cfce1ec842b9a5b1b0791a52afd18b833f114a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/93cfce1ec842b9a5b1b0791a52afd18b833f114a", + "reference": "93cfce1ec842b9a5b1b0791a52afd18b833f114a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=7.0" + }, + "require-dev": { + "composer/composer": "^1.0 || ^2.0", + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "EasyWeChatComposer\\Plugin" + }, + "autoload": { + "psr-4": { + "EasyWeChatComposer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "egulias/email-validator", - "version": "2.1.11", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23", + "name": "张铭阳", + "email": "mingyoungcheung@gmail.com" + } + ], + "description": "The composer plugin for EasyWeChat", + "support": { + "issues": "https://github.com/mingyoung/easywechat-composer/issues", + "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.0" + }, + "time": "2020-07-23T11:06:47+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.11", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/92dd169c32f6f55ba570c309d83f5209cefb5e23", "reference": "92dd169c32f6f55ba570c309d83f5209cefb5e23", "shasum": "", "mirrors": [ @@ -1022,25 +1076,172 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", - "keywords": [ - "cron", - "schedule" - ], - "abandoned": "dragonmantank/cron-expression", - "time": "2017-01-23T04:29:33+00:00" + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "abandoned": "dragonmantank/cron-expression", + "time": "2017-01-23T04:29:33+00:00" }, + { + "name": "overtrue/socialite", + "version": "2.0.22", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "0ce3285293026a639de317a70b01eeef051e9962" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/0ce3285293026a639de317a70b01eeef051e9962", + "reference": "0ce3285293026a639de317a70b01eeef051e9962", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^5.0|^6.0|^7.0", + "php": ">=5.6", + "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0" + }, + "conflict": { + "socialiteproviders/weixin": "*" + }, + "require-dev": { + "mockery/mockery": "~1.2", + "phpunit/phpunit": "~6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "paragonie/random_compat", - "version": "v9.99.99", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.", + "keywords": [ + "login", + "oauth", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/2.0.22" + }, + "funding": [ + { + "url": "https://www.patreon.com/overtrue", + "type": "patreon" + } + ], + "time": "2020-11-12T23:23:15+00:00" + }, + { + "name": "overtrue/wechat", + "version": "4.3.3", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "121607188e1cb1039a5ea0f49bcec011cb44dbdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/121607188e1cb1039a5ea0f49bcec011cb44dbdd", + "reference": "121607188e1cb1039a5ea0f49bcec011cb44dbdd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "easywechat-composer/easywechat-composer": "^1.1", + "ext-fileinfo": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.2 || ^7.0", + "monolog/monolog": "^1.22 || ^2.0", + "overtrue/socialite": "~2.0", + "php": ">=7.2", + "pimple/pimple": "^3.0", + "psr/simple-cache": "^1.0", + "symfony/cache": "^3.3 || ^4.3 || ^5.0", + "symfony/event-dispatcher": "^4.3 || ^5.0", + "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.15", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2.3", + "phpstan/phpstan": "^0.12.0", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + }, + "files": [ + "src/Kernel/Support/Helpers.php", + "src/Kernel/Helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/4.3.3" + }, + "time": "2020-12-07T08:20:11+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "", "mirrors": [ @@ -1208,26 +1409,85 @@ "homepage": "https://github.com/phalcon/incubator/graphs/contributors" } ], - "description": "Adapters, prototypes or functionality that can be potentially incorporated to the C-framework.", - "homepage": "https://phalconphp.com", - "keywords": [ - "framework", - "incubator", - "phalcon" - ], - "time": "2019-09-16T13:54:24+00:00" + "description": "Adapters, prototypes or functionality that can be potentially incorporated to the C-framework.", + "homepage": "https://phalconphp.com", + "keywords": [ + "framework", + "incubator", + "phalcon" + ], + "time": "2019-09-16T13:54:24+00:00" }, + { + "name": "pimple/pimple", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/21e45061c3429b1e06233475cc0e1f6fc774d5b0", + "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.3.1" + }, + "time": "2020-11-24T20:35:42+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", "shasum": "", "mirrors": [ @@ -1457,27 +1717,27 @@ "php": ">=5.3.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.jp/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", "keywords": [ "cache", "caching", @@ -1775,26 +2035,212 @@ "email": "fabien@symfony.com" } ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "time": "2019-04-21T09:21:45+00:00" + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2019-04-21T09:21:45+00:00" + }, + { + "name": "symfony/cache", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "c15fd2b3dcf2bd7d5ee3265874870d6cc694306b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/c15fd2b3dcf2bd7d5ee3265874870d6cc694306b", + "reference": "c15fd2b3dcf2bd7d5ee3265874870d6cc694306b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "~1.0", + "psr/log": "^1.1", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.10", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "^1.6", + "doctrine/dbal": "^2.10|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/filesystem": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "symfony/config", - "version": "v5.1.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "cf63f0613a6c6918e96db39c07a43b01e19a0773" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/cf63f0613a6c6918e96db39c07a43b01e19a0773", + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v5.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-11-21T09:39:55+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "8034ca0b61d4dd967f3698aaa1da2507b631d0cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/8034ca0b61d4dd967f3698aaa1da2507b631d0cb", + "reference": "8034ca0b61d4dd967f3698aaa1da2507b631d0cb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/config", + "version": "v5.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "cf63f0613a6c6918e96db39c07a43b01e19a0773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/cf63f0613a6c6918e96db39c07a43b01e19a0773", "reference": "cf63f0613a6c6918e96db39c07a43b01e19a0773", "shasum": "", "mirrors": [ @@ -1967,33 +2413,33 @@ ], "time": "2020-07-06T13:18:39+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.1.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5e20b83385a77593259c9f8beb2c43cd03b2ac14", - "reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" }, "thanks": { "name": "symfony/contracts", @@ -2035,31 +2481,31 @@ "type": "tidelift" } ], - "time": "2020-06-06T08:49:21+00:00" + "time": "2020-09-07T11:33:47+00:00" }, - { - "name": "symfony/event-dispatcher", - "version": "v4.3.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/429d0a1451d4c9c4abe1959b2986b88794b9b7d2", - "reference": "429d0a1451d4c9c4abe1959b2986b88794b9b7d2", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "f029d6f21eac61ab23198e7aca40e7638e8c8924" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f029d6f21eac61ab23198e7aca40e7638e8c8924", + "reference": "f029d6f21eac61ab23198e7aca40e7638e8c8924", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { - "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4" @@ -2069,24 +2515,20 @@ "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -2101,40 +2543,54 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2019-08-26T08:55:16+00:00" + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c", - "reference": "c61766f4440ca687de1084a5c00b08e167a2575c", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-31T22:44:29+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { - "php": "^7.1.3" + "php": ">=7.1.3" }, "suggest": { "psr/event-dispatcher": "", @@ -2142,9 +2598,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } + "branch-alias": { + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } }, "autoload": { "psr-4": { @@ -2165,18 +2625,32 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-06-20T06:46:26+00:00" + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-07-06T13:19:58+00:00" + }, { "name": "symfony/filesystem", "version": "v5.1.3", @@ -2247,26 +2721,26 @@ ], "time": "2020-05-30T20:35:19+00:00" }, - { - "name": "symfony/http-foundation", - "version": "v5.1.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "6cca6b2e4b69fc5bace160d14cf1ee5f71483db4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6cca6b2e4b69fc5bace160d14cf1ee5f71483db4", - "reference": "6cca6b2e4b69fc5bace160d14cf1ee5f71483db4", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + { + "name": "symfony/http-foundation", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e4576271ee99123aa59a40564c7b5405f0ebd1e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e4576271ee99123aa59a40564c7b5405f0ebd1e6", + "reference": "e4576271ee99123aa59a40564c7b5405f0ebd1e6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", @@ -2283,11 +2757,6 @@ "symfony/mime": "To use the file extension guesser" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" @@ -2326,7 +2795,7 @@ "type": "tidelift" } ], - "time": "2020-09-13T05:01:27+00:00" + "time": "2020-11-27T06:13:25+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2653,28 +3122,28 @@ ], "time": "2020-07-14T12:35:20+00:00" }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.18.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a", - "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-mbstring": "For best performance" @@ -2682,7 +3151,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.20-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2734,7 +3203,7 @@ "type": "tidelift" } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2020-10-23T14:02:19+00:00" }, { "name": "symfony/polyfill-php70", @@ -2980,33 +3449,33 @@ ], "time": "2020-07-14T12:35:20+00:00" }, - { - "name": "symfony/polyfill-php80", - "version": "v1.18.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981", - "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + { + "name": "symfony/polyfill-php80", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { - "php": ">=7.0.8" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-main": "1.20-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3055,37 +3524,125 @@ "url": "https://symfony.com/sponsor", "type": "custom" }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-07-14T12:35:20+00:00" + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "51a21cb3ba3927d4b4bf8f25cc55763351af5f2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/51a21cb3ba3927d4b4bf8f25cc55763351af5f2e", + "reference": "51a21cb3ba3927d4b4bf8f25cc55763351af5f2e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "symfony/phpunit-bridge": "^4.4 || ^5.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "symfony/service-contracts", - "version": "v2.1.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442", - "reference": "58c7475e5457c5492c26cc740cc0ad7464be9442", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-29T08:17:46+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, "require": { "php": ">=7.2.5", "psr/container": "^1.0" @@ -3096,7 +3653,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" }, "thanks": { "name": "symfony/contracts", @@ -3137,26 +3694,105 @@ "url": "https://symfony.com/sponsor", "type": "custom" }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], - "time": "2020-07-06T13:23:11+00:00" + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "fbc3507f23d263d75417e09a12d77c009f39676c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/fbc3507f23d263d75417e09a12d77c009f39676c", + "reference": "fbc3507f23d263d75417e09a12d77c009f39676c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "tencentcloud/tencentcloud-sdk-php", - "version": "3.0.251", - "source": { - "type": "git", - "url": "https://github.com/TencentCloud/tencentcloud-sdk-php.git", - "reference": "a3b3054262e48776e8014d5e385a8932b0102f29" - }, - "dist": { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-28T21:31:18+00:00" + }, + { + "name": "tencentcloud/tencentcloud-sdk-php", + "version": "3.0.251", + "source": { + "type": "git", + "url": "https://github.com/TencentCloud/tencentcloud-sdk-php.git", + "reference": "a3b3054262e48776e8014d5e385a8932b0102f29" + }, + "dist": { "type": "zip", "url": "https://api.github.com/repos/TencentCloud/tencentcloud-sdk-php/zipball/a3b3054262e48776e8014d5e385a8932b0102f29", "reference": "a3b3054262e48776e8014d5e385a8932b0102f29", @@ -3630,27 +4266,27 @@ "reference": "65144f2b0fad32b182ccb062b1efc1b4edea5d44", "shasum": "", "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } ] }, - "require": { - "php": ">=5.3.0" + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "notification-url": "https://packagist.jp/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Phalcon Team", + "email": "team@phalconphp.com", + "homepage": "https://phalconphp.com/en/team" }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Phalcon Team", - "email": "team@phalconphp.com", - "homepage": "https://phalconphp.com/en/team" - }, - { + { "name": "Contributors", "homepage": "https://github.com/phalcon/ide-stubs/graphs/contributors" } @@ -3741,5 +4377,5 @@ "ext-fileinfo": "*" }, "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/db/migrations/20201205091213_create_connect_table.php b/db/migrations/20201205091213_create_connect_table.php index 53041625..4e0419da 100644 --- a/db/migrations/20201205091213_create_connect_table.php +++ b/db/migrations/20201205091213_create_connect_table.php @@ -84,7 +84,7 @@ class CreateConnectTable extends Phinx\Migration\AbstractMigration 'after' => 'create_time', ]) ->addIndex(['open_id', 'provider'], [ - 'name' => 'openid_provider', + 'name' => 'open_provider', 'unique' => false, ]) ->create(); diff --git a/db/migrations/20201212102844_schema_202012121830.php b/db/migrations/20201212102844_schema_202012121830.php new file mode 100644 index 00000000..90512438 --- /dev/null +++ b/db/migrations/20201212102844_schema_202012121830.php @@ -0,0 +1,99 @@ +table('kg_consult') + ->addColumn('replier_id', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '回复者编号', + 'after' => 'owner_id', + ]) + ->save(); + $this->table('kg_connect') + ->addColumn('union_id', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 50, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => 'union_id', + 'after' => 'user_id', + ]) + ->addIndex(['union_id', 'provider'], [ + 'name' => 'union_provider', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->save(); + $this->table('kg_wechat_subscribe', [ + 'id' => false, + 'primary_key' => ['id'], + 'engine' => 'InnoDB', + 'encoding' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '', + 'row_format' => 'DYNAMIC', + ]) + ->addColumn('id', 'integer', [ + 'null' => false, + 'limit' => MysqlAdapter::INT_REGULAR, + 'identity' => 'enable', + 'comment' => '主键编号', + ]) + ->addColumn('user_id', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '用户编号', + 'after' => 'id', + ]) + ->addColumn('open_id', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 50, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '开放ID', + 'after' => 'user_id', + ]) + ->addColumn('deleted', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '删除标识', + 'after' => 'open_id', + ]) + ->addColumn('create_time', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '创建时间', + 'after' => 'deleted', + ]) + ->addColumn('update_time', 'integer', [ + 'null' => false, + 'default' => '0', + 'limit' => MysqlAdapter::INT_REGULAR, + 'comment' => '更新时间', + 'after' => 'create_time', + ]) + ->addIndex(['open_id'], [ + 'name' => 'open_id', + 'unique' => false, + ]) + ->addIndex(['user_id'], [ + 'name' => 'user_id', + 'unique' => false, + ]) + ->create(); + } +} diff --git a/db/migrations/20201212112717_data_202012121830.php b/db/migrations/20201212112717_data_202012121830.php new file mode 100644 index 00000000..88f41687 --- /dev/null +++ b/db/migrations/20201212112717_data_202012121830.php @@ -0,0 +1,81 @@ + 'wechat.oa', + 'item_key' => 'enabled', + 'item_value' => '0', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'app_id', + 'item_value' => '', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'app_secret', + 'item_value' => '', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'app_token', + 'item_value' => '', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'aes_key', + 'item_value' => '', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'notify_url', + 'item_value' => '', + ], + [ + 'section' => 'wechat.oa', + 'item_key' => 'notice_template', + 'item_value' => '{"account_login":"","order_finish":"","refund_finish":"","live_begin":"","consult_reply":""}', + ], + ]; + + $this->table('kg_setting')->insert($rows)->save(); + + $this->updateSmsTemplate(); + } + + public function down() + { + $this->getQueryBuilder()->delete('kg_setting')->where(['section' => 'wechat.oa'])->execute(); + } + + protected function updateSmsTemplate() + { + $table = 'kg_setting'; + + $where = ['section' => 'sms', 'item_key' => 'template']; + + $setting = $this->getQueryBuilder()->select('*')->from($table)->where($where)->execute()->fetch('assoc'); + + $itemValue = json_decode($setting['item_value'], true); + + $newItemValue = json_encode([ + 'verify' => $itemValue['verify'], + 'order_finish' => $itemValue['order'], + 'refund_finish' => $itemValue['refund'], + 'live_begin' => $itemValue['live'], + 'consult_reply' => '', + ]); + + $this->getQueryBuilder()->update($table)->where($where)->set('item_value', $newItemValue)->execute(); + } + +} \ No newline at end of file diff --git a/public/static/home/css/common.css b/public/static/home/css/common.css index bf8478c5..aa001338 100644 --- a/public/static/home/css/common.css +++ b/public/static/home/css/common.css @@ -1609,6 +1609,24 @@ margin: 0 10px; } +.my-subscribe { + margin-bottom: 15px; +} + +.my-subscribe .qrcode { + margin: 30px auto; + width: 160px; + height: 160px; +} + +.my-subscribe .tips { + text-align: center; +} + +.my-subscribe .success { + color: green; +} + .order-filter { padding: 15px 20px; } diff --git a/public/static/home/js/user.console.subscribe.js b/public/static/home/js/user.console.subscribe.js new file mode 100644 index 00000000..14dc9fd2 --- /dev/null +++ b/public/static/home/js/user.console.subscribe.js @@ -0,0 +1,29 @@ +layui.use(['jquery'], function () { + + var $ = layui.jquery; + var subscribed = $('input[name=subscribed]').val(); + var interval = null; + + if (subscribed === '0') { + showQrCode(); + interval = setInterval(function () { + queryStatus(); + }, 5000); + } + + function showQrCode() { + $.get('/wechat/oa/subscribe/qrcode', function (res) { + $('#sub-qrcode').html('扫码关注'); + }); + } + + function queryStatus() { + $.get('/wechat/oa/subscribe/status', function (res) { + if (res.status === 1) { + clearInterval(interval); + $('#sub-tips').addClass('success').html('关注公众号成功'); + } + }); + } + +}); \ No newline at end of file diff --git a/scheduler.php b/scheduler.php index b5b27246..f363c2e7 100644 --- a/scheduler.php +++ b/scheduler.php @@ -13,8 +13,8 @@ $bin = '/usr/local/bin/php'; $scheduler->php($script, $bin, ['--task' => 'deliver', '--action' => 'main']) ->at('*/3 * * * *'); -$scheduler->php($script, $bin, ['--task' => 'live_notify', '--action' => 'main']) - ->at('*/5 * * * *'); +$scheduler->php($script, $bin, ['--task' => 'notice', '--action' => 'main']) + ->at('*/3 * * * *'); $scheduler->php($script, $bin, ['--task' => 'sync_learning', '--action' => 'main']) ->at('*/7 * * * *'); @@ -49,9 +49,6 @@ $scheduler->php($script, $bin, ['--task' => 'unlock_user', '--action' => 'main'] $scheduler->php($script, $bin, ['--task' => 'revoke_vip', '--action' => 'main']) ->daily(3, 11); -$scheduler->php($script, $bin, ['--task' => 'clean_token', '--action' => 'main']) - ->daily(3, 17); - $scheduler->php($script, $bin, ['--task' => 'sitemap', '--action' => 'main']) ->daily(4, 3);