<?php /** * 更新用户常规任务进度 * * 按照 /Frontend/Index/UpdateUserTask 接口改来 */ namespace Frontend\Controller\Crontab; use Common\Common\Constant; use Common\Common\Integral; use Common\Common\Msg; use Common\Common\TaskCenter; use Common\Service\CustomtaskService; use Common\Service\DailytaskService; use Common\Service\UserActionService; use Common\Service\UserContentService; use Common\Service\UserNoticeService; use Common\Service\UserRewardService; use Common\Service\UserTaskService; use VcySDK\Member; use VcySDK\Service; /** * Class UpdateUserTaskController * @package Frontend\Controller\Crontab * @property UserTaskService $userTaskServ * @property UserContentService $userContentServ * @property UserActionService $useractionServ * @property CustomtaskService $customtaskServ * @property DailytaskService $dailyTaskServ * @property UserRewardService $userRewardServ * @property UserNoticeService $userNoticeServ */ class UpdateUserTaskController extends AbstractController { protected $userTaskServ = null; protected $userContentServ = null; protected $useractionServ = null; protected $customtaskServ = null; protected $dailyTaskServ = null; protected $userRewardServ = null; protected $userNoticeServ = null; /** * 单次处理人数 */ const DEAL_MEMBER_COUNT = 100; /** * 任务完成进度数值 */ const TASK_COMPLETE_PROGRESS = 100; /** * 当前企业标识 * @var string */ private $domain = ''; public function Index() { set_time_limit(0); $taskCenter = TaskCenter::instance(); $uids = []; // 获取用户ID while (count($uids) < self::DEAL_MEMBER_COUNT) { $uid = $taskCenter->pop(TaskCenter::CACHE_KEY); if ($uid === false) { break; } $uids[] = $uid; } if (empty($uids)) { exit('OK'); } $this->userTaskServ = new UserTaskService(); $this->userContentServ = new UserContentService(); $this->useractionServ = new UserActionService(); $this->customtaskServ = new CustomtaskService(); $this->dailyTaskServ = new DailytaskService(); $this->userRewardServ = new UserRewardService(); $this->userNoticeServ = new UserNoticeService(); // 初始化 SDK (减少重复) $config = array( 'apiUrl' => cfg('UC_APIURL'), 'enumber' => '', 'pluginIdentifier' => 'yuanquan', 'thirdIdentifier' => cfg('SDK_THIRD_IDENTIFIER'), 'logPath' => RUNTIME_PATH . '/Logs/VcySDK/', 'apiSecret' => cfg('API_SECRET'), 'apiSigExpire' => cfg('API_SIG_EXPIRE'), 'fileConvertApiUrl' => cfg('FILE_CONVERT_API_URL') ); Service::instance()->initSdk($config); // 同步人员进度 $this->syncProgress($uids); // 检查每日任务 $this->checkDailytaskProgress($uids); exit('OK'); } /** * 同步人员进度 * @param $uids * @return bool */ private function syncProgress($uids) { // 获取进行中未完成的任务列表 $usertaskList = $this->userTaskServ->listWithOutDomian([ 'uid' => $uids, 'complete_status' => Constant::USER_TASK_COMPLETE_STATUS_PROCESS, 'task_status' => Constant::CUSTOMTASK_STATUS_PROCESS, ], null, [], implode(',', [ 'user_task_id', 'uid', 'username', 'customtask_id', 'progress', 'domain', ])); if (empty($usertaskList)) { return true; } // 按照企业标识分组 $domainWithUserTask = []; foreach ($usertaskList as $item) { $domainWithUserTask[$item['domain']][] = $item; } unset($usertaskList); $service = Service::instance(); foreach ($domainWithUserTask as $this->domain => $item) { // 初始化 SDK $config['enumber'] = $this->domain; $service->setConfig($config); // 每个企业标识单独处理 try { $this->dealWithSingleDomain($item); } catch (\Exception $e) { } } } /** * 处理单个 Domain 下的用户任务数据 * @param $item */ private function dealWithSingleDomain($item) { /** * 当前企业下未完成的任务 ID 为 key 每个任务下对应的人员 (注意: 对应的人员数据不是该任务下的全部人员) * 第一步 ($item): * { * "任务ID" => { * "用户ID" => { * 用户任务数据 * } * ... * } * ... * customtask_id, uid * } */ $customtaskIdList = array_unique(array_column($item, 'customtask_id')); $uidList = array_unique(array_column($item, 'uid')); $temp = []; foreach ($item as $value) { $temp[$value['customtask_id']][$value['uid']] = $value; } $item = $temp; unset($temp); /** * 第二步 获取用户动作 ($userActions): * { * "用户ID" => { * "任务ID" => { * "app 标识" => { * "app data id" => "数据创建时间" * ... * } * ... * } * ... * } * } */ $useractionList = $this->useractionServ->listWithOutDomian([ 'uid' => $uidList, 'customtask_id' => $customtaskIdList, ], null, [], implode(',', ['customtask_id', 'app', 'app_data_id', 'created', 'uid',])); // 格式化动作数据 $userActions = []; foreach ($useractionList as $action) { $userActions[$action['uid']][$action['customtask_id']][$action['app']][$action['app_data_id']] = $action['created']; } unset($useractionList); /** * 第三步: 获取任务内容 (把任务内容放入第一步的数组内, 并且根据第二步的用户动作来判断是否完成任务) * { * "任务ID" => { * "用户ID" => { * 用户任务数据 * "用户任务内容" => [ * * ] * } * ... * } * ... * } */ $userContentList = $this->userContentServ->listWithOutDomian([ 'uid' => $uidList, 'customtask_id' => $customtaskIdList, ], null, [], implode(',', [ 'customtask_id', 'uid', 'username', 'app', 'app_data_id', 'user_content_id', 'content_status', 'complete_time' ])); // 任务内容 放入用户任务中 foreach ($userContentList as $userContent) { // 初始化用户任务完成度 if (!isset($item[$userContent['customtask_id']][$userContent['uid']]['completeCount'])) { $item[$userContent['customtask_id']][$userContent['uid']]['completeCount'] = 0; } // 如果完成 if ($userContent['content_status'] == Constant::USER_CONTENT_STATUS_COMPLETE) { // 增加完成任务数 $item[$userContent['customtask_id']][$userContent['uid']]['completeCount'] ++; } else { $customTaskId = $userContent['customtask_id']; $app = $userContent['app']; $appDataId = $userContent['app_data_id']; // 用户动作已存在 if (isset($userActions[$userContent['uid']][$customTaskId][$app][$appDataId])) { $completeTime = $userActions[$userContent['uid']][$customTaskId][$app][$appDataId]; // 更新内容完成状态为:已完成 $this->userContentServ->updateWithOutDomain($userContent['user_content_id'], [ 'content_status' => Constant::USER_CONTENT_STATUS_COMPLETE, 'complete_time' => $completeTime, ]); // 标记完成时间 $userContent['complete_time'] = $completeTime; // 增加完成任务数 $item[$userContent['customtask_id']][$userContent['uid']]['completeCount'] ++; } } // 放入统计数组内 $item[$userContent['customtask_id']][$userContent['uid']]['contents'][] = $userContent; } // 最后: 统计任务进度 (计算人员完成情况 并更新) $completedTask = []; foreach ($item as $taskId => $task) { foreach ($task as $userId => $userTask) { // 计算任务进度,精确到小数点后两位 $progress = round($userTask['completeCount'] / count($userTask['contents']), 2) * 100; // 任务进度有变更 if ($progress != $userTask['progress']) { $updateData = ['progress' => $progress]; // 如果完成了 if ($progress == self::TASK_COMPLETE_PROGRESS) { $updateData['complete_status'] = Constant::USER_TASK_COMPLETE_STATUS_COMPLETE; $updateData['complete_time'] = max(array_column($userTask['contents'], 'complete_time')); // 记录有人完成了 的任务ID $completedTask[$userTask['customtask_id']][$userId] = [ 'username' => $userTask['username'], 'complete_time' => $updateData['complete_time'] ]; } $this->userTaskServ->updateWithOutDomain($userTask['user_task_id'], $updateData); // 单人完成所有任务后增加任务完成数 if ($progress == 100) { $this->customtaskServ->addCompleteTotal($userTask['customtask_id']); } } } } // 发放激励 try { $this->sendCustomtaskReward($completedTask); } catch (\Exception $e) {} } /** * 发放激励 * @param $completedTask * { * "任务ID" => { * "人员ID" => { * "人员姓名", * "完成时间" * } * } * } * @return bool */ private function sendCustomtaskReward($completedTask) { if (empty($completedTask)) { return true; } $taskIdArr = array_keys($completedTask); $taskList = $this->customtaskServ->listWithOutDomian( [ 'customtask_id' => $taskIdArr ], null, [], implode(',', [ 'customtask_id', 'reward_setting', 'task_name' ]) ); $taskList = array_combine_by_key($taskList, 'customtask_id'); $integralServ = &Integral::instance(); $msgServ = &Msg::instance(); foreach ($completedTask as $taskId => $completeValue) { // 任务详情 $taskDetail = $taskList[$taskId]; $rewardSetting = unserialize($taskDetail['reward_setting']); foreach ($completeValue as $userId => $completeItem) { switch ($rewardSetting['type']) { // 勋章 case Constant::REWARD_TYPE_MEDAL: // 获取勋章数据 $medal = $this->getMedal($rewardSetting['medal_id']); if (empty($medal)) { break; } // 发放勋章 $rpcUrl = sprintf( '%s%s/%s%s', cfg('PROTOCAL'), $_SERVER['HTTP_HOST'], $this->domain, '/Integral/Rpc/Medal/Endow' ); $rpcParams = [ $rewardSetting['medal_id'], $userId, $completeItem['username'] ]; $rpcRe = \Com\Rpc::phprpc($rpcUrl)->invoke('Index', $rpcParams); if ($rpcRe) { $msg_data = [ [ 'title' => "恭喜您,获得【{$medal['name']}】勋章", 'description' => "获取渠道:员圈任务-{$taskDetail['task_name']}\n获取时间:" . rgmdate($completeItem['complete_time']), 'url' => oaUrl('Frontend/Index/Medal/Index', [], $this->domain), ], ]; $msgServ->sendNews($userId, null, null, $msg_data); } break; // 积分 case Constant::REWARD_TYPE_INTEGRAL: $integralServ->asynUpdateIntegral([ 'memUid' => $userId, 'irKey' => 'taskcenter_customtask_complete', 'msgIdentifier' => 'yuanquan', 'integral' => $rewardSetting['integral'], 'remark' => '完成常规任务获得激励', 'businessKey' => 'task_center', 'businessAct' => 'normal_task', ]); break; } } } return true; } /** * 检查每日任务进度,如果完成则通知用户 * @param $uids * @return bool */ private function checkDailytaskProgress($uids) { // 获取用户今日动作 $time = rstrtotime(rgmdate(MILLI_TIME, 'Y-m-d'), 1); $actionList = $this->useractionServ->listWithOutDomian([ 'uid' => $uids, 'created >= ?' => $time ]); // 按照 uid 为 key 重组 $temp = []; foreach ($actionList as $value) { $temp[$value['domain']][$value['uid']][] = $value; } $actionList = $temp; unset($temp); $service = Service::instance(); foreach ($actionList as $this->domain => $userAction) { // 初始化 SDK $config['enumber'] = $this->domain; $service->setConfig($config); // 每个企业标识单独处理 $this-> dealWithSingleDomainDeaily($userAction); } return true; } /** * 每个企业标识单独处理 每日任务 * @param $userAction array 人员 - 动作 * @return bool */ private function dealWithSingleDomainDeaily($userAction) { // 构建每日任务列表 $ruleList = $this->buildDailytaskList(); if (empty($ruleList)) { return true; } $uids = array_keys($userAction); // 获取通知列表 $noticeList = $this->userNoticeServ->listWithOutDomian([ 'uid' => $uids, 'created >= ?' => rstrtotime(rgmdate(MILLI_TIME, 'Y-m-d')), ]); // 格式化 $temp = []; foreach ($noticeList as $item) { $temp[$item['uid']][$item['app']][] = $item['rule_name']; } $noticeList = $temp; unset($temp); // 获取用户今日激励领取数据 $ruleNames = array_keys($ruleList); $time = rstrtotime(rgmdate(MILLI_TIME, 'Y-m-d'), 1); $rewardList = $this->userRewardServ->listWithOutDomian([ 'uid' => $uids, 'rule_name' => $ruleNames, 'created >= ?' => $time, ]); $temp = []; foreach ($rewardList as $item) { $temp[$item['uid']][$item['app']][] = $item['rule_name']; } $rewardList = $temp; unset($temp); // 获取人员数据 $userList = (new Member(Service::instance()))->listAll(['memUids' => $uids], 1, self::DEAL_MEMBER_COUNT); $userList = array_combine_by_key($userList['list'], 'memUid'); // 需要发送消息 $noticeInsertList = []; // 按一个个人来 foreach ($userList as $uid => $user) { foreach ($ruleList as $name => $rule) { // // 允许重复性 $unique true: 多条数据算一条 false: 多条数据算多条 // $unique = isset($rule['config']['unique']) ? $rule['config']['unique'] : false; // 人员行为 foreach ($userAction[$uid] as $action) { if (!in_array($action['action_key'], $rule['action_keys'])) { continue; } $user['completed'][$action['action_key']] ++; // 完成任务 if ($user['completed'][$action['action_key']] >= $rule['count'] && // 没有发送过消息 (!in_array($action['action_key'], $noticeList[$uid][$rule['app']])) && // 没有获取过奖励 (!in_array($action['action_key'], $rewardList[$uid][$rule['app']]))) { $noticeInsertList[] = [ 'uid' => $uid, 'username' => $userList[$uid]['memUsername'], 'app' => $action['app'], 'rule_name' => $action['action_key'], // insert_all 补全字段信息 时就不会去自动补全了 'domain' => $this->domain ]; // 发送消息 $this->sendDailytaskCompleteMsg([ 'app' => $action['app'], 'integral' => $rule['integral'], 'complete_time' => rgmdate(max(array_column($userAction[$uid], 'created')), 'Y-m-d H:i'), 'task_title' => $rule['task_title'] ], $uid); } } } } // 记录发送过的消息 $this->userNoticeServ->insert_all($noticeInsertList); return true; } /** * 构建每日任务列表 * @author zhonglei * @param array $user 用户信息 * @return array * + string app 应用 * + string app_url 应用Url * + string rule_name 规则名称 * + string task_title 任务名称 * + int integral 积分 * + int require_total 需要完成总数 * + int complete_total 已完成总数 * + int is_get 是否已领取激励(1=未领取;2=已领取) */ private function buildDailytaskList() { $configList = $this->dailyTaskServ->listWithOutDomian([ 'app' => [ Constant::APP_WORKMATE, Constant::APP_ANSWER ], 'domain' => $this->domain ]); // 获取已启用的任务规则 $ruleList = []; foreach ($configList as $config) { // 任务未开启 if ($config['is_open'] != Constant::DAILYTASK_IS_OPEN_TRUE) { continue; } // 格式化任务名称 $config['rules'] = unserialize($config['rules']); foreach ($config['rules'] as $name => $rule) { $taskTitle = ''; switch ($name) { // 员圈发表话题 case Constant::RULE_NAME_WORKMATE_CIRCLE: $taskTitle = '在员圈发表%s个话题'; break; // 员圈添加评论 case Constant::RULE_NAME_WORKMATE_COMMENT: $unique = isset($rule['config']['unique']) ? $rule['config']['unique'] : false; $taskTitle = $unique ? '在员圈不同话题下添加%s条评论' : '在员圈添加%s条评论'; break; // 员圈点赞 case Constant::RULE_NAME_WORKMATE_LIKE: $unique = isset($rule['config']['unique']) ? $rule['config']['unique'] : false; $taskTitle = $unique ? '在员圈对话题进行%s个点赞' : '在员圈进行%s个点赞'; break; // 问答中心发起提问 case Constant::RULE_NAME_ANSWER_QUESTION: $taskTitle = '在问答中心发起%s个提问'; break; // 问答中心添加回答 case Constant::RULE_NAME_ANSWER_ANSWER: $unique = isset($rule['config']['unique']) ? $rule['config']['unique'] : false; $taskTitle = $unique ? '在问答中心不同问题下添加%s个回答' : '在问答中心添加%s个回答'; break; } $rule['app'] = $config['app']; $rule['task_title'] = sprintf($taskTitle, $rule['count']); $ruleList[$name] = $rule; } } return $ruleList; } /** * 获取勋章详情 * @param int $imId 勋章ID * @return array */ private function getMedal($imId = 0) { $rpcUrl = sprintf( '%s%s/%s%s', cfg('PROTOCAL'), $_SERVER['HTTP_HOST'], $this->domain, '/Integral/Rpc/Medal/List' ); $list = call_user_func(array(\Com\Rpc::phprpc($rpcUrl), 'Index')); if (!empty($list)) { $list = array_combine_by_key($list, 'im_id'); } $medalInfo = isset($list[$imId]) ? $list[$imId] : []; return $medalInfo; } /** * 发送每日任务完成消息 * @author zhonglei * @param array $dailytask 每日任务数据 * @param string $uid 用户ID * @return bool */ private function sendDailytaskCompleteMsg($dailytask, $uid) { // 消息通知前缀 $prefix = cfg('NOTICE_PREFIX', null, ''); // 应用名称 $app_names = [ Constant::APP_WORKMATE => '员圈', Constant::APP_ANSWER => '问答中心', ]; $app_name = isset($app_names[$dailytask['app']]) ? $app_names[$dailytask['app']] : ''; $reward = "+{$dailytask['integral']}积分"; $msgServ = &Msg::instance(); $msg_data = [ [ 'title' => "{$prefix}您已完成了{$app_name}的日常任务,快来领取奖励吧", 'description' => "任务名称:{$dailytask['task_title']}\n积分奖励:{$reward}\n完成时间:{$dailytask['complete_time']}", 'url' => oaUrl('Frontend/Index/TaskIndex', [], $this->domain), ], ]; try { $msgServ->sendNews($uid, null, null, $msg_data); } catch (\Exception $e) { // 不影响流程 } return true; } }