1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-24 20:06:09 +08:00

Merge branch 'develop'

This commit is contained in:
xiaochong0302 2020-12-23 12:13:10 +08:00
commit b6808d7294
67 changed files with 3233 additions and 692 deletions

View File

@ -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微信微博第三方登录
- 代码优化以及问题修复

View File

@ -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**

View File

@ -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();

View File

@ -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);
}
/**
* 清理日志文件
*

View File

@ -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;

View File

@ -1,81 +0,0 @@
<?php
namespace App\Console\Tasks;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\Chapter as ChapterRepo;
use App\Services\LiveNotify as LiveNotifyService;
use App\Services\Sms\Live as LiveSms;
class LiveNotifyTask extends Task
{
public function mainAction()
{
$redis = $this->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');
}
}

View File

@ -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)
{

View File

@ -0,0 +1,137 @@
<?php
namespace App\Console\Tasks;
use App\Models\Task as TaskModel;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNotice;
use App\Services\Logic\Notice\ConsultReply as ConsultReplyNotice;
use App\Services\Logic\Notice\LiveBegin as LiveBeginNotice;
use App\Services\Logic\Notice\OrderFinish as OrderFinishNotice;
use App\Services\Logic\Notice\RefundFinish as RefundFinishNotice;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class NoticeTask extends Task
{
const TRY_COUNT = 3;
public function mainAction()
{
$logger = $this->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();
}
}

View File

@ -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);
}
/**

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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',
],
],
],
],

View File

@ -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();

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -36,7 +36,7 @@
{% set show_url = url({'for':'admin.order.show','id':item.id}) %}
<tr>
<td>
<p>商品:{{ item.subject }} {{ item_type(item.item_type) }}</p>
<p>商品:{{ item.subject }}</p>
<p>单号:{{ item.sn }}</p>
</td>
<td>

View File

@ -53,21 +53,27 @@
</tr>
<tr>
<td>订单通知</td>
<td><input class="layui-input" type="text" name="template[order]" value="{{ template.order }}" lay-verify="required"></td>
<td><input id="tc-order" class="layui-input" type="text" value="下单成功,商品名称:{1},订单序号:{2},订单金额:¥{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-order">复制</span></td>
<td><input class="layui-input" type="text" name="template[order_finish]" value="{{ template.order_finish }}" lay-verify="required"></td>
<td><input id="tc-order-finish" class="layui-input" type="text" value="下单成功,商品名称:{1},订单序号:{2},订单金额:¥{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-order-finish">复制</span></td>
</tr>
<tr>
<td>退款通知</td>
<td><input class="layui-input" type="text" name="template[refund]" value="{{ template.refund }}" lay-verify="required"></td>
<td><input id="tc-refund" class="layui-input" type="text" value="退款成功,商品名称:{1}订单序号:{2},退款金额:¥{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-refund">复制</span></td>
<td><input class="layui-input" type="text" name="template[refund_finish]" value="{{ template.refund_finish }}" lay-verify="required"></td>
<td><input id="tc-refund-finish" class="layui-input" type="text" value="退款成功,商品名称:{1}退款序号:{2},退款金额:¥{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-refund-finish">复制</span></td>
</tr>
<tr>
<td>直播通知</td>
<td><input class="layui-input" type="text" name="template[live]" value="{{ template.live }}" lay-verify="required"></td>
<td><input id="tc-live" class="layui-input" type="text" value="直播预告,课程名称:{1},章节名称:{2},开播时间:{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-live">复制</span></td>
<td>直播提醒</td>
<td><input class="layui-input" type="text" name="template[live_begin]" value="{{ template.live_begin }}" lay-verify="required"></td>
<td><input id="tc-live-begin" class="layui-input" type="text" value="直播预告,课程名称:{1},章节名称:{2},开播时间:{3}" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-live-begin">复制</span></td>
</tr>
<tr>
<td>回复通知</td>
<td><input class="layui-input" type="text" name="template[consult_reply]" value="{{ template.consult_reply }}" lay-verify="required"></td>
<td><input id="tc-consult-reply" class="layui-input" type="text" value="{1} 回复了你的咨询,课程名称:{2},请登录系统查看详情。" readonly="readonly"></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc-consult-reply">复制</span></td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,16 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">公众号</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('setting/wechat_oa') }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,104 @@
{% set notice_template = oa.notice_template|json_decode %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.wechat'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启</label>
<div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if oa.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if oa.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="app_id" value="{{ oa.app_id }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Secret</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="app_secret" value="{{ oa.app_secret }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App Token</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="app_token" value="{{ oa.app_token }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Aes Key</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="aes_key" value="{{ oa.aes_key }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Notify Url</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="notify_url" value="{{ oa.notify_url }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="wechat.oa">
</div>
</div>
</form>
<fieldset class="layui-elem-field layui-field-title">
<legend>模板配置</legend>
</fieldset>
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.wechat'}) }}">
<table class="layui-table kg-table layui-form">
<colgroup>
<col width="12%">
<col width="30%">
<col>
</colgroup>
<thead>
<tr>
<th>名称</th>
<th>模板编号</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>登录提醒</td>
<td><input class="layui-input" type="text" name="notice_template[account_login]" value="{{ notice_template.account_login }}" lay-verify="required"></td>
<td></td>
</tr>
<tr>
<td>订单通知</td>
<td><input class="layui-input" type="text" name="notice_template[order_finish]" value="{{ notice_template.order_finish }}" lay-verify="required"></td>
<td></td>
</tr>
<tr>
<td>退款通知</td>
<td><input class="layui-input" type="text" name="notice_template[refund_finish]" value="{{ notice_template.refund_finish }}" lay-verify="required"></td>
<td></td>
</tr>
<tr>
<td>直播提醒</td>
<td><input class="layui-input" type="text" name="notice_template[live_begin]" value="{{ notice_template.live_begin }}" lay-verify="required"></td>
<td></td>
</tr>
<tr>
<td>回复通知</td>
<td><input class="layui-input" type="text" name="notice_template[consult_reply]" value="{{ notice_template.consult_reply }}" lay-verify="required"></td>
<td></td>
</tr>
</tbody>
</table>
<br>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="wechat.oa">
</div>
</div>
</form>

View File

@ -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);
}
}

View File

@ -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")
*/

View File

@ -0,0 +1,76 @@
<?php
namespace App\Http\Home\Controllers;
use App\Http\Home\Services\WechatOfficialAccount as WechatOAService;
use App\Traits\Response as ResponseTrait;
/**
* @RoutePrefix("/wechat/oa")
*/
class WechatOfficialAccountController extends \Phalcon\Mvc\Controller
{
use ResponseTrait;
/**
* @Get("/subscribe/status", name="home.wechat.oa.sub_status")
*/
public function subscribeStatusAction()
{
$service = new WechatOAService();
$status = $service->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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,238 @@
<?php
namespace App\Http\Home\Services;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Wechat as WechatService;
use App\Validators\User as UserValidator;
use EasyWeChat\Kernel\Messages\Text as TextMessage;
class WechatOfficialAccount extends Service
{
public function getOfficialAccount()
{
$service = new WechatService();
return $service->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();
}
}
}
}

View File

@ -52,6 +52,9 @@
<ul class="my-menu">
<li><a href="{{ url({'for':'home.uc.profile'}) }}">个人信息</a></li>
<li><a href="{{ url({'for':'home.uc.account'}) }}">帐号安全</a></li>
{% if wechat_oa.enabled == 1 %}
<li><a href="{{ url({'for':'home.uc.subscribe'}) }}">关注订阅</a></li>
{% endif %}
</ul>
</div>
</div>

View File

@ -0,0 +1,33 @@
{% extends 'templates/main.volt' %}
{% block content %}
<div class="layout-main clearfix">
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">关注订阅</span>
</div>
<div class="my-subscribe">
{% if subscribed == 0 %}
<div id="sub-qrcode" class="qrcode"></div>
<div id="sub-tips" class="tips">订阅官方公众号,接收重要通知!</div>
{% else %}
<div class="tips">你已经订阅官方公众号</div>
{% endif %}
</div>
<div class="layui-hide">
<input type="hidden" name="subscribed" value="{{ subscribed }}">
</div>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('home/js/user.console.subscribe.js') }}
{% endblock %}

View File

@ -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)
{

View File

@ -36,12 +36,19 @@ class Consult extends Model
public $chapter_id;
/**
* 用户编号
* 提主编号
*
* @var int
*/
public $owner_id;
/**
* 答主编号
*
* @var int
*/
public $replier_id;
/**
* 提问
*

View File

@ -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();

View File

@ -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; // 咨询回复通知
/**
* 优先级
*/

View File

@ -0,0 +1,79 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class WechatSubscribe extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 用户编号
*
* @var int
*/
public $user_id;
/**
* 开放ID
*
* @var string
*/
public $open_id;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_wechat_subscribe';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Repos;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use Phalcon\Mvc\Model;
class WechatSubscribe extends Repository
{
/**
* @param int $userId
* @param string $openId
* @return WechatSubscribeModel|Model|bool
*/
public function findSubscribe($userId, $openId)
{
return WechatSubscribeModel::findFirst([
'conditions' => '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],
]);
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Services\Auth;
use App\Models\User as UserModel;
use App\Services\Auth as AuthService;
class Mobile extends AuthService
{
public function saveAuthInfo(UserModel $user)
{
$authKey = $this->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';
}
}

View File

@ -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;
}
}

View File

@ -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)

View File

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

View File

@ -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);
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Services\Logic\Notice;
use App\Models\Task as TaskModel;
use App\Models\User as UserModel;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Wechat\Notice\AccountLogin as WechatAccountLoginNotice;
use App\Traits\Client as ClientTrait;
class AccountLogin extends LogicService
{
use ClientTrait;
public function handleTask(TaskModel $task)
{
$params = $task->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();
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Services\Logic\Notice;
use App\Models\Consult as ConsultModel;
use App\Models\Task as TaskModel;
use App\Repos\Consult as ConsultRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Sms\Notice\ConsultReply as SmsConsultReplyNotice;
use App\Services\Wechat\Notice\ConsultReply as WechatConsultReplyNotice;
class ConsultReply extends LogicService
{
public function handleTask(TaskModel $task)
{
$consultId = $task->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();
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Services\Logic\Notice;
use App\Models\Chapter as ChapterModel;
use App\Models\CourseUser as CourseUserModel;
use App\Models\Task as TaskModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Sms\Notice\LiveBegin as SmsLiveBeginNotice;
use App\Services\Wechat\Notice\LiveBegin as WechatLiveBeginNotice;
class LiveBegin extends LogicService
{
public function handleTask(TaskModel $task)
{
$courseUser = $task->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();
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Services\Logic\Notice;
use App\Models\Order as OrderModel;
use App\Models\Task as TaskModel;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Sms\Notice\OrderFinish as SmsOrderFinishNotice;
use App\Services\Wechat\Notice\OrderFinish as WechatOrderFinishNotice;
class OrderFinish extends LogicService
{
public function handleTask(TaskModel $task)
{
$orderId = $task->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();
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Services\Logic\Notice;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel;
use App\Repos\Refund as RefundRepo;
use App\Repos\User as UserRepo;
use App\Repos\WechatSubscribe as WechatSubscribeRepo;
use App\Services\Logic\Service as LogicService;
use App\Services\Sms\Notice\RefundFinish as SmsRefundFinishNotice;
use App\Services\Wechat\Notice\RefundFinish as WechatRefundFinishNotice;
class RefundFinish extends LogicService
{
public function handleTask(TaskModel $task)
{
$refundId = $task->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();
}
}

View File

@ -1,50 +0,0 @@
<?php
namespace App\Services\Sms;
use App\Repos\Account as AccountRepo;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\Smser;
class Live extends Smser
{
protected $templateCode = 'live';
/**
* @param int $chapterId
* @param int $userId
* @param int $startTime
* @return bool
*/
public function handle($chapterId, $userId, $startTime)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Services\Sms\Notice;
use App\Models\User as UserModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class ConsultReply extends Smser
{
protected $templateCode = 'consult_reply';
/**
* @param UserModel $user
* @param array $params
* @return bool|null
*/
public function handle(UserModel $user, array $params)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Services\Sms\Notice;
use App\Models\User as UserModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class LiveBegin extends Smser
{
protected $templateCode = 'live_begin';
/**
* @param UserModel $user
* @param array $params
* @return bool|null
*/
public function handle(UserModel $user, array $params)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Services\Sms\Notice;
use App\Models\User as UserModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class OrderFinish extends Smser
{
protected $templateCode = 'order_finish';
/**
* @param UserModel $user
* @param array $params
* @return bool|null
*/
public function handle(UserModel $user, array $params)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Services\Sms\Notice;
use App\Models\User as UserModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class RefundFinish extends Smser
{
protected $templateCode = 'refund_finish';
/**
* @param UserModel $user
* @param array $params
* @return bool|null
*/
public function handle(UserModel $user, array $params)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Services\Sms;
use App\Models\Order as OrderModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class Order extends Smser
{
protected $templateCode = 'order';
/**
* @param OrderModel $order
* @return bool
*/
public function handle(OrderModel $order)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Services\Sms;
use App\Models\Refund as RefundModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class Refund extends Smser
{
protected $templateCode = 'refund';
/**
* @param RefundModel $refund
* @return bool
*/
public function handle(RefundModel $refund)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->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);
}
}

59
app/Services/Wechat.php Normal file
View File

@ -0,0 +1,59 @@
<?php
namespace App\Services;
use EasyWeChat\Factory;
use Phalcon\Logger\Adapter\File as FileLogger;
class Wechat extends Service
{
/**
* @var FileLogger
*/
public $logger;
public function __construct()
{
$this->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',
],
]
];
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Services\Wechat\Notice;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Services\WechatNotice;
class AccountLogin extends WechatNotice
{
protected $templateCode = 'account_login';
/**
* @param WechatSubscribeModel $subscribe
* @param array $params
* @return bool
*/
public function handle(WechatSubscribeModel $subscribe, array $params)
{
$first = '你好,登录系统成功!';
$remark = '如果非本人操作,请立即修改密码哦!';
$loginRegion = implode('/', [
$params['login_region']['country'],
$params['login_region']['province'],
$params['login_region']['city'],
]);
$loginTime = date('Y-m-d H:i:s', $params['login_time']);
$params = [
'first' => $first,
'remark' => $remark,
'keyword1' => $loginRegion,
'keyword2' => $loginTime,
];
$templateId = $this->getTemplateId($this->templateCode);
return $this->send($subscribe->open_id, $templateId, $params);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\Wechat\Notice;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Services\WechatNotice;
class ConsultReply extends WechatNotice
{
protected $templateCode = 'consult_reply';
/**
* @param WechatSubscribeModel $subscribe
* @param array $params
* @return bool
*/
public function handle(WechatSubscribeModel $subscribe, array $params)
{
$first = sprintf('%s 回复了你的咨询!', $params['replier']['name']);
$remark = '如果还有其它疑问,请和我们保持联系哦!';
$params = [
'first' => $first,
'remark' => $remark,
'keyword1' => $params['course']['title'],
];
$templateId = $this->getTemplateId($this->templateCode);
return $this->send($subscribe->open_id, $templateId, $params);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services\Wechat\Notice;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Services\WechatNotice;
class LiveBegin extends WechatNotice
{
protected $templateCode = 'live_begin';
/**
* @param WechatSubscribeModel $subscribe
* @param array $params
* @return bool
*/
public function handle(WechatSubscribeModel $subscribe, array $params)
{
$first = '你参与的课程直播就要开始了!';
$remark = '如果没能参与直播,记得观看直播录像哦!';
$params = [
'first' => $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);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Services\Wechat\Notice;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Services\WechatNotice;
class OrderFinish extends WechatNotice
{
protected $templateCode = 'order_finish';
/**
* @param WechatSubscribeModel $subscribe
* @param array $params
* @return bool
*/
public function handle(WechatSubscribeModel $subscribe, $params)
{
$first = '订单已处理完成!';
$remark = '感谢您的支持,有疑问请联系客服哦!';
$params = [
'first' => $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);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services\Wechat\Notice;
use App\Models\WechatSubscribe as WechatSubscribeModel;
use App\Services\WechatNotice;
class RefundFinish extends WechatNotice
{
protected $templateCode = 'refund_finish';
/**
* @param WechatSubscribeModel $subscribe
* @param array $params
* @return bool
*/
public function handle(WechatSubscribeModel $subscribe, array $params)
{
$first = '退款已处理完成!';
$remark = '感谢您的支持,有疑问请联系客服哦!';
$params = [
'first' => $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);
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Services;
use App\Services\Wechat as WechatService;
use Phalcon\Logger\Adapter\File as FileLogger;
Abstract class WechatNotice extends Service
{
/**
* @var array
*/
protected $settings;
/**
* @var FileLogger
*/
protected $logger;
public function __construct()
{
$this->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;
}
}

View File

@ -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",

1364
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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();

View File

@ -0,0 +1,99 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class Schema202012121830 extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->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();
}
}

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class Data202012121830 extends AbstractMigration
{
public function up()
{
$rows = [
[
'section' => '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();
}
}

View File

@ -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;
}

View File

@ -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('<img alt="扫码关注" src="' + res.qrcode + '">');
});
}
function queryStatus() {
$.get('/wechat/oa/subscribe/status', function (res) {
if (res.status === 1) {
clearInterval(interval);
$('#sub-tips').addClass('success').html('关注公众号成功');
}
});
}
});

View File

@ -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);